From 3333e3e6d9c22858904cb62838778ecf64942c3f Mon Sep 17 00:00:00 2001 From: Erik Little Date: Thu, 12 Feb 2015 11:14:38 -0500 Subject: [PATCH 01/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2309504..7400fdf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Socket.IO-Client-Swift ====================== -Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ +Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1 Installation ============ From 6e8a32361c0ac3fa49d564582c05acc1ee17a1d8 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Thu, 19 Feb 2015 11:21:54 -0500 Subject: [PATCH 02/64] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7400fdf..9d655d3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ Socket.IO-Client-Swift ====================== -Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1 +Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1. + +For Swift 1.2, use the 1.2 branch. (1.2 will be merged with master once Swift 1.2 is released) Installation ============ From 082c00dbb1837c7fb6428b71e6d353b4873fef3f Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 19 Feb 2015 22:52:22 -0500 Subject: [PATCH 03/64] backport new stuff from 1.2 branch. Adds acks --- README.md | 29 +- SwiftIO/SocketAckHandler.swift | 47 ++++ SwiftIO/SocketEvent.swift | 103 +++++-- SwiftIO/SocketEventHandler.swift | 9 +- SwiftIO/SocketIOClient.swift | 456 ++++++++++++++++++++++++------- SwiftIO/SwiftRegex.swift | 71 ++--- 6 files changed, 545 insertions(+), 170 deletions(-) create mode 100644 SwiftIO/SocketAckHandler.swift diff --git a/README.md b/README.md index 9d655d3..b5f4dab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Socket.IO-Client-Swift Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1. -For Swift 1.2, use the 1.2 branch. (1.2 will be merged with master once Swift 1.2 is released) +For Swift 1.2 use the 1.2 branch. Installation ============ @@ -18,11 +18,12 @@ Constructor `init(socketURL: String, opts[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) Methods ------- -1. `socket.on(name:String, callback:((data:AnyObject?) -> Void))` - Adds a handler for an event. -2. `socket.onMultipleItems(name:String, callback:((data:NSArray?) -> Void))` - Adds a handler for an event that can have multiple items. Items are stored in an array. +1. `socket.on(name:String, callback:((data:AnyObject?) -> Void)) -> SocketAckHandler` - Adds a handler for an event. Returns a SocketAckHandler which can be used to ack an event. See example. +2. `socket.onMultipleItems(name:String, callback:((data:NSArray?) -> Void)) -> SocketAckHandler` - Adds a handler for an event that can have multiple items. Items are stored in an array. Returns a SocketAckHandler which can be used to ack an event. See example. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. -5. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. +6. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ @@ -32,6 +33,8 @@ Events 4. `reconnect` - Emitted when the connection is starting to reconnect. 5. `reconnectAttempt` - Emitted when attempting to reconnect. +Example +======= ```swift // opts can be omitted, will use default values let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ @@ -44,7 +47,7 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ // Socket Events socket.on("connect") {data in println("socket connected") - + // Sending messages socket.emit("testEcho") @@ -57,6 +60,18 @@ socket.on("connect") {data in true, ["test": "foo"], "bar") } +// Requesting acks, and adding ack args +socket.on("ackEvent") {data in + if let str = data as? String { + println("Got ackEvent") + } + + socket.emitWithAck("ackTest", "test").onAck {data in + println(data) + } + +}.ackWith("I got your event", "dude") + socket.on("disconnect") {data in if let reason = data as? String { println("Socket disconnected: \(reason)") @@ -101,7 +116,7 @@ socket.onMultipleItems("multipleItems") {data in println(obj["test"]) } } - + // Recieving binary socket.on("dataTest") {data in if let data = data as? NSData { diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift new file mode 100644 index 0000000..d3bfe77 --- /dev/null +++ b/SwiftIO/SocketAckHandler.swift @@ -0,0 +1,47 @@ +// +// SocketAckHandler.swift +// Socket.IO-Swift +// +// Created by Erik Little on 2/14/15. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +typealias AckCallback = (AnyObject?) -> Void + +class SocketAckHandler { + let ackNum:Int! + let event:String! + var ackData:[AnyObject]? + var callback:AckCallback? + + init(event:String, ackNum:Int = 0) { + self.ackNum = ackNum + self.event = event + } + + func onAck(callback:AckCallback) { + self.callback = callback + } + + func ackWith(data:AnyObject...) { + self.ackData = data + } +} \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 679044b..84ee896 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -25,16 +25,20 @@ import Foundation class SocketEvent { + let justAck:Bool! + var ack:Int? var args:AnyObject! lazy var currentPlace = 0 lazy var datas = [NSData]() var event:String! var placeholders:Int! - init(event:String, args:AnyObject?, placeholders:Int = 0) { + init(event:String, args:AnyObject?, placeholders:Int = 0, ackNum:Int? = nil, justAck:Bool = false) { self.event = event - self.args = args? + self.args = args self.placeholders = placeholders + self.ack = ackNum + self.justAck = justAck } func addData(data:NSData) -> Bool { @@ -62,46 +66,101 @@ class SocketEvent { } class func createMessageForEvent(event:String, withArgs args:[AnyObject], - hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?) -> String { + hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?, wantsAck ack:Int? = nil) -> String { var message:String var jsonSendError:NSError? if !hasBinary { if nsp == nil { - message = "42[\"\(event)\"" + if ack == nil { + message = "42[\"\(event)\"," + } else { + message = "42\(ack!)[\"\(event)\"," + } } else { - message = "42/\(nsp!),[\"\(event)\"" + if ack == nil { + message = "42/\(nsp!),[\"\(event)\"," + } else { + message = "42/\(nsp!),\(ack!)[\"\(event)\"," + } } } else { if nsp == nil { - message = "45\(datas)-[\"\(event)\"" + if ack == nil { + message = "45\(datas)-[\"\(event)\"," + } else { + message = "45\(datas)-\(ack!)[\"\(event)\"," + } } else { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + if ack == nil { + message = "45\(datas)-/\(nsp!),[\"\(event)\"," + } else { + message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"," + } } } - for arg in args { - message += "," - - if arg is NSDictionary || arg is [AnyObject] { - let jsonSend = NSJSONSerialization.dataWithJSONObject(arg, - options: NSJSONWritingOptions(0), error: &jsonSendError) - let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) + return self.completeMessage(message, args: args) + } + + class func createAck(ack:Int, withEvent event:String, withArgs args:[AnyObject], + withAckType ackType:Int, withNsp nsp:String, withBinary binary:Int = 0) -> String { + var msg:String + + if ackType == 3 { + if nsp == "/" { + msg = "43\(ack)[" - message += jsonString! - continue + return self.completeMessage(msg, args: args) + + } else { + msg = "43/\(nsp),\(ack)[" + + return self.completeMessage(msg, args: args) } - - if arg is String { - message += "\"\(arg)\"" - continue + } else { + if nsp == "/" { + msg = "46\(binary)-\(ack)[" + + return self.completeMessage(msg, args: args) + + } else { + msg = "46\(binary)-/\(nsp),\(ack)[" + + return self.completeMessage(msg, args: args) } + } + } + + private class func completeMessage(var message:String, args:[AnyObject]) -> String { + var err:NSError? + for arg in args { + + if arg is NSDictionary || arg is [AnyObject] { + let jsonSend = NSJSONSerialization.dataWithJSONObject(arg, + options: NSJSONWritingOptions(0), error: &err) + let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) - message += "\(arg)" + message += jsonString! as String + message += "," + continue } - return message + "]" + if arg is String { + message += "\"\(arg)\"" + message += "," + continue + } + + message += "\(arg)" + message += "," + } + + if message != "" { + message.removeAtIndex(message.endIndex.predecessor()) + } + return message + "]" } private func fillInArray(arr:NSArray) -> NSArray { diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 8a77b1f..a13add6 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,20 +23,25 @@ // THE SOFTWARE. class SocketEventHandler { + let ack:SocketAckHandler! let event:String! let callback:NormalCallback? let callbackMult:MultipleCallback? var multiEvent = false - init(event:String, callback:NormalCallback) { + init(event:String, callback:NormalCallback, ack:SocketAckHandler) { self.event = event self.callback = callback + self.callbackMult = nil + self.ack = ack } - init(event:String, callback:MultipleCallback) { + init(event:String, callback:MultipleCallback, ack:SocketAckHandler) { self.event = event self.callbackMult = callback + self.callback = nil self.multiEvent = true + self.ack = ack } func executeCallback(item:AnyObject?, items:NSArray? = nil) { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8c607f4..de32c7f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -28,11 +28,19 @@ typealias NormalCallback = (AnyObject?) -> Void typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { - let socketURL:String! - private let secure:Bool! + let socketURL:NSMutableString! + let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + let handleQueue = dispatch_queue_create("handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + private var ackHandlers = [SocketAckHandler]() + private var currentAck = -1 private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var pingTimer:NSTimer! + private var secure = false var closed = false var connected = false var connecting = false @@ -45,16 +53,15 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var sid:String? init(socketURL:String, opts:[String: AnyObject]? = nil) { - super.init() var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { self.secure = true - } else { - self.secure = false } + mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" + self.socketURL = mutURL // Set options @@ -96,10 +103,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = false var endpoint:String - if self.secure! { - endpoint = "wss://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + if self.secure { + endpoint = "wss://\(self.socketURL)/socket.io/?transport=websocket" } else { - endpoint = "ws://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + endpoint = "ws://\(self.socketURL)/socket.io/?transport=websocket" } self.io = SRWebSocket(URL: NSURL(string: endpoint)) @@ -124,87 +131,147 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args) + } + } + + func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { + if !self.connected { + return SocketAckHandler(event: "fail") + } + + self.currentAck++ + let ackHandler = SocketAckHandler(event: event, ackNum: self.currentAck) + self.ackHandlers.append(ackHandler) + + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args, ack: true) + } + + return ackHandler + } + + private func _emit(event:String, _ args:[AnyObject], ack:Bool = false) { var frame:SocketEvent var str:String - var items = [AnyObject](count: args.count, repeatedValue: 1) - var numberOfPlaceholders = -1 - var hasBinary = false - var emitDatas = [NSData]() - for i in 0.. SocketAckHandler { + let ackHandler = SocketAckHandler(event: name) + let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) self.handlers.append(handler) + + return ackHandler } // Adds handler for multiple arg message - func onMultipleItems(name:String, callback:MultipleCallback) { - let handler = SocketEventHandler(event: name, callback: callback) + func onMultipleItems(name:String, callback:MultipleCallback) -> SocketAckHandler { + let ackHandler = SocketAckHandler(event: name) + let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) self.handlers.append(handler) + + return ackHandler } // Opens the connection to the socket @@ -296,6 +369,59 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return parsed } + private class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { + var items = [AnyObject](count: args.count, repeatedValue: 1) + var numberOfPlaceholders = -1 + var hasBinary = false + var emitDatas = [NSData]() + + for i in 0.. (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() @@ -354,7 +480,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Check for successful namepsace connect if self.nsp != nil { if stringMessage == "40/\(self.nsp!)" { - self.handleEvent(event: "connect", data: nil) + self.handleEvent("connect", data: nil) return } } @@ -384,17 +510,25 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { /** Begin check for message **/ - let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\[.*\\])?"].groups() + let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - if messageGroups[1] == "42" { + if messageGroups[1].hasPrefix("42") { + var mesNum = messageGroups[1] + var ackNum:String var namespace:String? var messagePart:String! - if messageGroups.count == 4 { - namespace = messageGroups[2] - messagePart = messageGroups[3] + if messageGroups[3] != "" { + ackNum = messageGroups[3] + } else { + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 2)) + mesNum.replaceRange(range, with: "") + ackNum = mesNum } + namespace = messageGroups[2] + messagePart = messageGroups[4] + if namespace == "" && self.nsp != nil { return } @@ -413,7 +547,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { - self.handleEvent(event: event, data: parsed) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } else if let strData = data { // There are multiple items in the message @@ -421,7 +561,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // parseData to try and get an array. let asArray = "[\(strData)]" if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - self.handleEvent(event: event, data: parsed, multipleItems: true) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -431,9 +577,34 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() if noItemMessage != nil && noItemMessage.count == 2 { let event = noItemMessage[1] - self.handleEvent(event: event, data: nil, multipleItems: false) + if ackNum == "" { + self.handleEvent(event, data: nil) + } else { + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } + } else if messageGroups[1].hasPrefix("43") { + let arr = Array(messageGroups[1]) + var ackNum:String + let nsp = messageGroups[2] + + if nsp == "" && self.nsp != nil { + return + } + + if nsp == "" { + ackNum = String(arr[2...arr.count-1]) + } else { + ackNum = messageGroups[3] + } + + let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) + self.handleAck(ackNum.toInt()!, data: ackData) + + return } /** End Check for message @@ -463,23 +634,34 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { /** Begin check for binary placeholders **/ - let binaryGroup = mutMessage["(\\d*)-\\/?(\\w*)?,?\\[(\".*?\"),(.*)\\]$"].groups() + let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$"].groups() - if binaryGroup != nil { + if binaryGroup == nil { + return + } + + if binaryGroup[1].hasPrefix("45") { // println(binaryGroup) - var event:String! - var mutMessageObject:NSMutableString! + var ackNum:String + var event:String + var mutMessageObject:NSMutableString var namespace:String? + var numberOfPlaceholders:String let messageType = RegexMutable(binaryGroup[1]) - let numberOfPlaceholders = messageType["45"] ~= "" - // Check if message came from a namespace - if binaryGroup.count == 5 { - namespace = binaryGroup[2] - event = RegexMutable(binaryGroup[3])["\""] ~= "" - mutMessageObject = RegexMutable(binaryGroup[4]) + namespace = binaryGroup[2] + if binaryGroup[3] != "" { + ackNum = binaryGroup[3] as String + } else if self.nsp == nil && binaryGroup[2] != "" { + ackNum = binaryGroup[2] + } else { + ackNum = "" } + numberOfPlaceholders = (messageType["45"] ~= "") as String + event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String + mutMessageObject = RegexMutable(binaryGroup[5]) + if namespace == "" && self.nsp != nil { self.lastSocketMessage = nil return @@ -488,9 +670,40 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.integerValue) + var mes:SocketEvent + if ackNum == "" { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!) + } else { + self.currentAck = ackNum.toInt()! + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) + } + self.lastSocketMessage = mes + } else if binaryGroup[1].hasPrefix("46") { + let messageType = RegexMutable(binaryGroup[1]) + let numberOfPlaceholders = (messageType["46"] ~= "") as String + var ackNum:String + var nsp:String + + if binaryGroup[3] == "" { + ackNum = binaryGroup[2] + nsp = "" + } else { + ackNum = binaryGroup[3] + nsp = binaryGroup[2] + } + + if nsp == "" && self.nsp != nil { + return + } + var mutMessageObject = RegexMutable(binaryGroup[5]) + let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] + ~= "\"~~$2\"" + + self.lastSocketMessage = SocketEvent(event: "", args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) } /** End check for binary placeholders @@ -508,11 +721,32 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) - self.handleEvent(event: event, data: filledInArgs) + + if self.lastSocketMessage!.justAck! { + self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + return + } + + if self.lastSocketMessage!.ack != nil { + self.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + } else { + self.handleEvent(event, data: filledInArgs) + } } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() - self.handleEvent(event: event, data: filledInArgs, multipleItems: true) - return + + if self.lastSocketMessage!.justAck! { + self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + return + } + + if self.lastSocketMessage!.ack != nil { + self.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + } else { + self.handleEvent(event, data: filledInArgs) + } } } } @@ -525,8 +759,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Starts the ping timer private func startPingTimer(#interval:Int) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) + dispatch_async(dispatch_get_main_queue()) { + self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, + selector: Selector("sendPing"), userInfo: nil, repeats: true) + } } // We lost connection and should attempt to reestablish @@ -535,7 +771,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connecting = false self.reconnects = false self.reconnecting = false - self.handleEvent(event: "disconnect", data: "Failed to reconnect") + self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) return } else if self.connected { self.connecting = false @@ -544,7 +780,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent(event: "reconnectAttempt", data: triesLeft) + self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -567,7 +803,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - self.parseSocketMessage(message) + dispatch_async(self.handleQueue) {[weak self] in + if self == nil { + return + } + + self?.parseSocketMessage(message) + } } // Called when the socket is opened @@ -583,7 +825,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - self.handleEvent(event: "connect", data: nil) + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.handleEvent("connect", data: nil) } // Called when the socket is closed @@ -592,9 +836,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent(event: "disconnect", data: reason) + self.handleEvent("disconnect", data: reason, isInternalMessage: true) } else { - self.handleEvent(event: "reconnect", data: reason) + self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } @@ -605,11 +849,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.connected = false self.connecting = false - self.handleEvent(event: "error", data: error.localizedDescription) + self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent(event: "disconnect", data: error.localizedDescription) + self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) } else if !self.reconnecting { - self.handleEvent(event: "reconnect", data: error.localizedDescription) + self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 96c03cf..16527ab 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -13,7 +13,7 @@ import Foundation -var swiftRegexCache = Dictionary() +var swiftRegexCache = [String: NSRegularExpression]() public class SwiftRegex: NSObject, BooleanType { @@ -60,15 +60,15 @@ public class SwiftRegex: NSObject, BooleanType { } public func range(options: NSMatchingOptions = nil) -> NSRange { - return regex.rangeOfFirstMatchInString(target, options: nil, range: targetRange) + return regex.rangeOfFirstMatchInString(target as String, options: nil, range: targetRange) } public func match(options: NSMatchingOptions = nil) -> String! { - return substring(range(options: options)) + return substring(range(options: options)) as String } public func groups(options: NSMatchingOptions = nil) -> [String]! { - return groupsForMatch( regex.firstMatchInString(target, options: options, range: targetRange) ) + return groupsForMatch( regex.firstMatchInString(target as String, options: options, range: targetRange) ) } func groupsForMatch(match: NSTextCheckingResult!) -> [String]! { @@ -93,9 +93,9 @@ public class SwiftRegex: NSObject, BooleanType { } set(newValue) { if let mutableTarget = target as? NSMutableString { - for match in matchResults().reverse() { + for match in matchResults()!.reverse() { let replacement = regex.replacementStringForResult( match, - inString: target, offset: 0, template: newValue ) + inString: target as String, offset: 0, template: newValue ) mutableTarget.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) } } else { @@ -104,26 +104,33 @@ public class SwiftRegex: NSObject, BooleanType { } } - func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult] { - return regex.matchesInString(target, options: options, range: targetRange) as [NSTextCheckingResult] + func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult]? { + let matches = regex.matchesInString(target as String, options: options, range: targetRange) + as? [NSTextCheckingResult] + + if matches != nil { + return matches! + } else { + return nil + } } public func ranges(options: NSMatchingOptions = nil) -> [NSRange] { - return matchResults(options: options).map { $0.range } + return matchResults(options: options)!.map { $0.range } } public func matches(options: NSMatchingOptions = nil) -> [String] { - return matchResults(options: options).map { self.substring($0.range) } + return matchResults(options: options)!.map( { self.substring($0.range) as String } ) } public func allGroups(options: NSMatchingOptions = nil) -> [[String]] { - return matchResults(options: options).map { self.groupsForMatch($0) } + return matchResults(options: options)!.map { self.groupsForMatch($0) } } public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { var out = Dictionary() - for match in matchResults(options: options) { - out[substring(match.rangeAtIndex(1))] = + for match in matchResults(options: options)! { + out[substring(match.rangeAtIndex(1)) as String] = substring(match.rangeAtIndex(2)) } return out @@ -134,19 +141,19 @@ public class SwiftRegex: NSObject, BooleanType { let out = NSMutableString() var pos = 0 - regex.enumerateMatchesInString(target, options: options, range: targetRange ) { + regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) { (match: NSTextCheckingResult!, flags: NSMatchingFlags, stop: UnsafeMutablePointer) in let matchRange = match.range - out.appendString( self.substring( NSRange(location:pos, length:matchRange.location-pos) ) ) + out.appendString( self.substring( NSRange(location:pos, length:matchRange.location-pos) ) as String ) out.appendString( substitution(match, stop) ) pos = matchRange.location + matchRange.length } - out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) ) + out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) as String ) if let mutableTarget = target as? NSMutableString { - mutableTarget.setString(out) + mutableTarget.setString(out as String) return mutableTarget } else { SwiftRegex.failure("Modify on non-mutable") @@ -208,43 +215,41 @@ extension String { } public func RegexMutable(string: NSString) -> NSMutableString { - return NSMutableString(string:string) + return NSMutableString(string:string as String) } public func ~= (left: SwiftRegex, right: String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop in return left.regex.replacementStringForResult( match, - inString: left.target, offset: 0, template: right ) - } + inString: left.target as String, offset: 0, template: right ) + }, options: nil) } public func ~= (left: SwiftRegex, right: [String]) -> NSMutableString { var matchNumber = 0 - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop -> String in if ++matchNumber == right.count { stop.memory = true } return left.regex.replacementStringForResult( match, - inString: left.target, offset: 0, template: right[matchNumber-1] ) - } + inString: left.target as String, offset: 0, template: right[matchNumber-1] ) + }, options: nil) } public func ~= (left: SwiftRegex, right: (String) -> String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in - return right(left.substring(match.range)) - } + // return right(left.substring(match.range)) + return left.substituteMatches( + {match, stop -> String in + right(left.substring(match.range) as String) + }, options: nil) } public func ~= (left: SwiftRegex, right: ([String]) -> String) -> NSMutableString { - return left.substituteMatches { - (match: NSTextCheckingResult, stop: UnsafeMutablePointer) in + return left.substituteMatches({match, stop -> String in return right(left.groupsForMatch(match)) - } + }, options: nil) } // my take on custom threading operators from From a1a5b054ca496fce2cf761afd9df724ea8664f00 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Mon, 23 Feb 2015 09:32:59 -0500 Subject: [PATCH 04/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5f4dab..8a52279 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ API === Constructor ----------- -`init(socketURL: String, opts[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) +`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) Methods ------- 1. `socket.on(name:String, callback:((data:AnyObject?) -> Void)) -> SocketAckHandler` - Adds a handler for an event. Returns a SocketAckHandler which can be used to ack an event. See example. From 2da5c3c9b310f5e7a2fb33520710c8a8630a5dfc Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 23 Feb 2015 14:55:25 -0500 Subject: [PATCH 05/64] API CHANGE. remove .onMultipleItems, .on now acts like .onMultipleItems; ack'ing is now done with a function passed into an event handler function. See example --- README.md | 40 ++++++++++++------------ SwiftIO/SocketAckHandler.swift | 5 --- SwiftIO/SocketEvent.swift | 21 +++++++++++-- SwiftIO/SocketEventHandler.swift | 45 +++++++++------------------ SwiftIO/SocketIOClient.swift | 52 +++++++++++++++----------------- 5 files changed, 78 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index b5f4dab..9e16034 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,7 @@ Constructor `init(socketURL: String, opts[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) Methods ------- -1. `socket.on(name:String, callback:((data:AnyObject?) -> Void)) -> SocketAckHandler` - Adds a handler for an event. Returns a SocketAckHandler which can be used to ack an event. See example. -2. `socket.onMultipleItems(name:String, callback:((data:NSArray?) -> Void)) -> SocketAckHandler` - Adds a handler for an event that can have multiple items. Items are stored in an array. Returns a SocketAckHandler which can be used to ack an event. See example. +1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. 4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. 5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. @@ -45,7 +44,7 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ ]) // Socket Events -socket.on("connect") {data in +socket.on("connect") {data, ack in println("socket connected") // Sending messages @@ -60,9 +59,9 @@ socket.on("connect") {data in true, ["test": "foo"], "bar") } -// Requesting acks, and adding ack args -socket.on("ackEvent") {data in - if let str = data as? String { +// Requesting acks, and responding to acks +socket.on("ackEvent") {data, ack in + if let str = data?[0] as? String { println("Got ackEvent") } @@ -70,36 +69,37 @@ socket.on("ackEvent") {data in println(data) } -}.ackWith("I got your event", "dude") + ack?("Got your event", "dude") +} -socket.on("disconnect") {data in - if let reason = data as? String { +socket.on("disconnect") {data, ack in + if let reason = data?[0] as? String { println("Socket disconnected: \(reason)") } } -socket.on("reconnect") {data in - if let reason = data as? String { +socket.on("reconnect") {data, ack in + if let reason = data?[0] as? String { println("Socket reconnecting: \(reason)") } } -socket.on("reconnectAttempt") {data in - if let triesLeft = data as? Int { +socket.on("reconnectAttempt") {data, ack in + if let triesLeft = data?[0] as? Int { println(triesLeft) } } // End Socket Events -socket.on("jsonTest") {data in - if let json = data as? NSDictionary { +socket.on("jsonTest") {data, ack in + if let json = data?[0] as? NSDictionary { println(json["test"]!) // foo bar } } // Messages that have multiple items are passed // by an array -socket.onMultipleItems("multipleItems") {data in +socket.on("multipleItems") {data, ack in if data == nil { return } @@ -118,14 +118,14 @@ socket.onMultipleItems("multipleItems") {data in } // Recieving binary -socket.on("dataTest") {data in - if let data = data as? NSData { +socket.on("dataTest") {data, ack in + if let data = data?[0] as? NSData { println("data is binary") } } -socket.on("objectDataTest") {data in - if let dict = data as? NSDictionary { +socket.on("objectDataTest") {data, ack in + if let dict = data?[0] as? NSDictionary { if let data = dict["data"] as? NSData { let string = NSString(data: data, encoding: NSUTF8StringEncoding) println("Got data: \(string!)") diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index d3bfe77..5a9d909 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -29,7 +29,6 @@ typealias AckCallback = (AnyObject?) -> Void class SocketAckHandler { let ackNum:Int! let event:String! - var ackData:[AnyObject]? var callback:AckCallback? init(event:String, ackNum:Int = 0) { @@ -40,8 +39,4 @@ class SocketAckHandler { func onAck(callback:AckCallback) { self.callback = callback } - - func ackWith(data:AnyObject...) { - self.ackData = data - } } \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 84ee896..6019c27 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -104,30 +104,47 @@ class SocketEvent { return self.completeMessage(message, args: args) } - class func createAck(ack:Int, withEvent event:String, withArgs args:[AnyObject], - withAckType ackType:Int, withNsp nsp:String, withBinary binary:Int = 0) -> String { + class func createAck(ack:Int, withArgs args:[AnyObject], withAckType ackType:Int, + withNsp nsp:String, withBinary binary:Int = 0) -> String { var msg:String if ackType == 3 { if nsp == "/" { msg = "43\(ack)[" + if args.count == 0 { + println(msg + "]") + return msg + "]" + } + return self.completeMessage(msg, args: args) } else { msg = "43/\(nsp),\(ack)[" + if args.count == 0 { + return msg + "]" + } + return self.completeMessage(msg, args: args) } } else { if nsp == "/" { msg = "46\(binary)-\(ack)[" + if args.count == 0 { + return msg + "]" + } + return self.completeMessage(msg, args: args) } else { msg = "46\(binary)-/\(nsp),\(ack)[" + if args.count == 0 { + return msg + "]" + } + return self.completeMessage(msg, args: args) } } diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index a13add6..3fe9204 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -22,43 +22,28 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +typealias AckEmitter = (AnyObject...) -> Void + +private func emitAckCallback(socket:SocketIOClient, num:Int, type:Int) -> AckEmitter { + func emitter(items:AnyObject...) { + socket.emitAck(num, withData: items, withAckType: type) + } + + return emitter +} + class SocketEventHandler { - let ack:SocketAckHandler! let event:String! let callback:NormalCallback? - let callbackMult:MultipleCallback? - var multiEvent = false - init(event:String, callback:NormalCallback, ack:SocketAckHandler) { + init(event:String, callback:NormalCallback) { self.event = event self.callback = callback - self.callbackMult = nil - self.ack = ack } - init(event:String, callback:MultipleCallback, ack:SocketAckHandler) { - self.event = event - self.callbackMult = callback - self.callback = nil - self.multiEvent = true - self.ack = ack - } - - func executeCallback(item:AnyObject?, items:NSArray? = nil) { - if self.multiEvent { - if items != nil { - callbackMult?(items) - } else if item != nil { - callbackMult?([item!]) - } else { - callbackMult?(nil) - } - } else { - if items != nil { - callback?(items) - } else { - callback?(item) - } - } + func executeCallback(_ items:NSArray? = nil, withAck ack:Int? = nil, withAckType type:Int? = nil, + withSocket socket:SocketIOClient? = nil) { + callback?(items, ack != nil ? emitAckCallback(socket!, ack!, type!) : nil) } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index de32c7f..f64e4f6 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,9 +24,6 @@ import Foundation -typealias NormalCallback = (AnyObject?) -> Void -typealias MultipleCallback = (NSArray?) -> Void - class SocketIOClient: NSObject, SRWebSocketDelegate { let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), @@ -197,7 +194,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // If the server wants to know that the client received data - private func emitAck(ack:Int, withEvent event:String, withData data:[AnyObject]?, withAckType ackType:Int) { + func emitAck(ack:Int, withData data:[AnyObject]?, withAckType ackType:Int) { dispatch_async(self.ackQueue) {[weak self] in if self == nil || !self!.connected || data == nil { return @@ -208,20 +205,20 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if !hasBinary { if self?.nsp == nil { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + str = SocketEvent.createAck(ack, withArgs: items, withAckType: 3, withNsp: "/") } else { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + str = SocketEvent.createAck(ack, withArgs: items, withAckType: 3, withNsp: self!.nsp!) } self?.io?.send(str) } else { if self?.nsp == nil { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + str = SocketEvent.createAck(ack, withArgs: items, withAckType: 6, withNsp: "/", withBinary: emitDatas.count) } else { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + str = SocketEvent.createAck(ack, withArgs: items, withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } @@ -255,16 +252,27 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { for handler in self.handlers { if handler.event == event { if data is NSArray { - handler.executeCallback(nil, items: (data as NSArray)) if ack != nil { - self.emitAck(ack!, withEvent: event, - withData: handler.ack.ackData, withAckType: ackType) + handler.executeCallback(data as? NSArray, withAck: ack!, + withAckType: ackType, withSocket: self) + } else { + handler.executeCallback(data as? NSArray) } } else { - handler.executeCallback(data) + + // Trying to do a ternary expression in the executeCallback method + // seemed to crash Swift + var dataArr:NSArray? = nil + + if let data:AnyObject = data { + dataArr = [data] + } + if ack != nil { - self.emitAck(ack!, withEvent: event, - withData: handler.ack.ackData, withAckType: ackType) + handler.executeCallback(dataArr, withAck: ack!, + withAckType: ackType, withSocket: self) + } else { + handler.executeCallback(dataArr) } } } @@ -279,21 +287,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Adds handler for single arg message - func on(name:String, callback:NormalCallback) -> SocketAckHandler { - let ackHandler = SocketAckHandler(event: name) - let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) + func on(name:String, callback:NormalCallback) { + let handler = SocketEventHandler(event: name, callback: callback) self.handlers.append(handler) - - return ackHandler - } - - // Adds handler for multiple arg message - func onMultipleItems(name:String, callback:MultipleCallback) -> SocketAckHandler { - let ackHandler = SocketAckHandler(event: name) - let handler = SocketEventHandler(event: name, callback: callback, ack: ackHandler) - self.handlers.append(handler) - - return ackHandler } // Opens the connection to the socket From 913c25eda17343bfb1ad9e4f91020f3a6e0564a4 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Mon, 23 Feb 2015 15:13:11 -0500 Subject: [PATCH 06/64] Remove println --- SwiftIO/SocketEvent.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 6019c27..dfc4d60 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -113,7 +113,6 @@ class SocketEvent { msg = "43\(ack)[" if args.count == 0 { - println(msg + "]") return msg + "]" } @@ -269,4 +268,4 @@ class SocketEvent { return false } -} \ No newline at end of file +} From 63cf32f30ea36386f350c00f4e4463d3c411340b Mon Sep 17 00:00:00 2001 From: Erik Little Date: Mon, 23 Feb 2015 17:22:43 -0500 Subject: [PATCH 07/64] Why did I do that --- SwiftIO/SocketEvent.swift | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index dfc4d60..308f99c 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -112,38 +112,22 @@ class SocketEvent { if nsp == "/" { msg = "43\(ack)[" - if args.count == 0 { - return msg + "]" - } - return self.completeMessage(msg, args: args) } else { msg = "43/\(nsp),\(ack)[" - if args.count == 0 { - return msg + "]" - } - return self.completeMessage(msg, args: args) } } else { if nsp == "/" { msg = "46\(binary)-\(ack)[" - if args.count == 0 { - return msg + "]" - } - return self.completeMessage(msg, args: args) } else { msg = "46\(binary)-/\(nsp),\(ack)[" - if args.count == 0 { - return msg + "]" - } - return self.completeMessage(msg, args: args) } } @@ -151,6 +135,11 @@ class SocketEvent { private class func completeMessage(var message:String, args:[AnyObject]) -> String { var err:NSError? + + if args.count == 0 { + return message + "]" + } + for arg in args { if arg is NSDictionary || arg is [AnyObject] { From 1bc761ac8da3bd80a70baf65389857abdfcb118a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 24 Feb 2015 12:51:27 -0500 Subject: [PATCH 08/64] #15 add connectWithParams --- README.md | 3 +- SwiftIO/SocketIOClient.swift | 55 +++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 90eb835..ff28994 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Methods 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. 4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. 5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. -6. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +6. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. +7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index f64e4f6..26b5fb2 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -32,10 +32,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { DISPATCH_QUEUE_SERIAL) let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private lazy var params:[String: AnyObject] = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() private var currentAck = -1 private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? + private var paramConnect = false private var pingTimer:NSTimer! private var secure = false var closed = false @@ -92,21 +94,41 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Connects to the server func connect() { + self.connectWithURL(self.createConnectURL()) + } + + // Connect to the server using params + func connectWithParams(params:[String: AnyObject]) { + self.params = params + self.paramConnect = true + var endpoint = self.createConnectURL() + + for (key, value) in params { + let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + endpoint += "&\(keyEsc)=" + + if value is String { + let valueEsc = (value as String).stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + endpoint += "\(valueEsc)" + } else { + endpoint += "\(value)" + } + } + + self.connectWithURL(endpoint) + } + + private func connectWithURL(url:String) { if self.closed { println("Warning: This socket was previvously closed. Reopening could be dangerous. Be careful.") } self.connecting = true self.closed = false - var endpoint:String - if self.secure { - endpoint = "wss://\(self.socketURL)/socket.io/?transport=websocket" - } else { - endpoint = "ws://\(self.socketURL)/socket.io/?transport=websocket" - } - - self.io = SRWebSocket(URL: NSURL(string: endpoint)) + self.io = SRWebSocket(URL: NSURL(string: url)) self.io?.delegate = self self.io?.open() } @@ -120,6 +142,14 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return mutData } + private func createConnectURL() -> String { + if self.secure { + return "wss://\(self.socketURL)/socket.io/?transport=websocket" + } else { + return "ws://\(self.socketURL)/socket.io/?transport=websocket" + } + } + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -286,7 +316,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - // Adds handler for single arg message + // Adds handler for an event func on(name:String, callback:NormalCallback) { let handler = SocketEventHandler(event: name, callback: callback) self.handlers.append(handler) @@ -794,7 +824,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self!.tryReconnect(triesLeft: triesLeft) } self.reconnecting = true - self.connect() + + if self.paramConnect { + self.connectWithParams(self.params) + } else { + self.connect() + } } // Called when a message is recieved From 6d5706c126abb1161381a2f4d2621de61d4a61df Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 13:26:30 -0500 Subject: [PATCH 09/64] send disconnect packet --- SwiftIO/SocketIOClient.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 26b5fb2..8b2169d 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -89,6 +89,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = true self.connecting = false self.connected = false + self.io?.send("41") self.io?.close() } @@ -888,4 +889,4 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.tryReconnect(triesLeft: self.reconnectAttempts) } } -} \ No newline at end of file +} From 0c63e6a1340dc490a04ecc928b1c89e9cc5a1134 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 15:17:19 -0500 Subject: [PATCH 10/64] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ff28994..ce733c7 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,7 @@ socket.on("jsonTest") {data, ack in } } -// Messages that have multiple items are passed -// by an array +// Event items are passed by an array socket.on("multipleItems") {data, ack in if data == nil { return From b4c61f4b47eaf1101e4b922085b3292eda0e9ece Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 18:13:57 -0500 Subject: [PATCH 11/64] Remove ackhandlers --- SwiftIO/SocketIOClient.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8b2169d..b835092 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -263,10 +263,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when the socket gets an ack for something it sent private func handleAck(ack:Int, data:AnyObject?) { - for handler in self.ackHandlers { - if handler.ackNum == ack { + self.ackHandlers = self.ackHandlers.filter {handler in + if handler.ackNum != ack { + return true + } else { handler.callback?(data) - break + return false } } } From 3d7540d5935046b607e99e8c92c157ea8328b88f Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 18:35:59 -0500 Subject: [PATCH 12/64] I was being stupid again --- SwiftIO/SocketEvent.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 308f99c..22465e6 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -111,26 +111,18 @@ class SocketEvent { if ackType == 3 { if nsp == "/" { msg = "43\(ack)[" - - return self.completeMessage(msg, args: args) - } else { msg = "43/\(nsp),\(ack)[" - - return self.completeMessage(msg, args: args) } } else { if nsp == "/" { msg = "46\(binary)-\(ack)[" - - return self.completeMessage(msg, args: args) - } else { msg = "46\(binary)-/\(nsp),\(ack)[" - - return self.completeMessage(msg, args: args) } } + + return self.completeMessage(msg, args: args) } private class func completeMessage(var message:String, args:[AnyObject]) -> String { From 761623a0f89ff24a141533577019b36dfa07dd1a Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 2 Mar 2015 13:59:22 -0500 Subject: [PATCH 13/64] fix emit with no args --- SwiftIO/SocketEvent.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 22465e6..26d8499 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -74,29 +74,29 @@ class SocketEvent { if !hasBinary { if nsp == nil { if ack == nil { - message = "42[\"\(event)\"," + message = "42[\"\(event)\"" } else { - message = "42\(ack!)[\"\(event)\"," + message = "42\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "42/\(nsp!),[\"\(event)\"," + message = "42/\(nsp!),[\"\(event)\"" } else { - message = "42/\(nsp!),\(ack!)[\"\(event)\"," + message = "42/\(nsp!),\(ack!)[\"\(event)\"" } } } else { if nsp == nil { if ack == nil { - message = "45\(datas)-[\"\(event)\"," + message = "45\(datas)-[\"\(event)\"" } else { - message = "45\(datas)-\(ack!)[\"\(event)\"," + message = "45\(datas)-\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "45\(datas)-/\(nsp!),[\"\(event)\"," + message = "45\(datas)-/\(nsp!),[\"\(event)\"" } else { - message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"," + message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"" } } } @@ -122,14 +122,16 @@ class SocketEvent { } } - return self.completeMessage(msg, args: args) + return self.completeMessage(msg, args: args, ack: true) } - private class func completeMessage(var message:String, args:[AnyObject]) -> String { + private class func completeMessage(var message:String, args:[AnyObject], ack:Bool = false) -> String { var err:NSError? if args.count == 0 { return message + "]" + } else if !ack { + message += "," } for arg in args { From 8bebc3c1594e78f089a262ef7783d9ca3525aed0 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 20:53:57 -0500 Subject: [PATCH 14/64] start work on adding polling --- SwiftIO/SocketEngine.swift | 190 +++++++++++++++++++++++++++++++++++ SwiftIO/SocketIOClient.swift | 22 +++- 2 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 SwiftIO/SocketEngine.swift diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift new file mode 100644 index 0000000..f9198be --- /dev/null +++ b/SwiftIO/SocketEngine.swift @@ -0,0 +1,190 @@ +// +// SocketEngine.swift +// Socket.IO-Swift +// +// Created by Erik Little on 3/3/15. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +private enum PacketType: String { + case OPEN = "0" + case CLOSE = "1" + case PING = "2" + case PONG = "3" + case MESSAGE = "4" + case UPGRADE = "5" + case NOOP = "6" +} + +class SocketEngine: NSObject, SRWebSocketDelegate { + unowned let client:SocketIOClient + private var pingTimer:NSTimer? + private let pollingQueue = NSOperationQueue() + private var _polling = true + private var _websocket = false + private var websocketConnected = false + var pingInterval:Int? + var polling:Bool { + return self._polling + } + var sid = "" + var websocket:Bool { + return self._websocket + } + var ws:SRWebSocket? + + init(client:SocketIOClient) { + self.client = client + } + + func open(opts:[String: AnyObject]? = nil) { + var url:String + var urlPolling:String + var urlWebSocket:String + + if self.client.secure { + url = "\(self.client.socketURL)/socket.io/?transport=" + urlPolling = "https://" + url + "polling" + urlWebSocket = "wss://" + url + "websocket" + } else { + url = "\(self.client.socketURL)/socket.io/?transport=" + urlPolling = "http://" + url + "polling" + urlWebSocket = "ws://" + url + "websocket" + } + + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling)!) + + NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in + var err:NSError? + + if self == nil || err != nil || data == nil { + println("Error") + println(err) + exit(1) + } + + let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) + + if let json = NSJSONSerialization.JSONObjectWithData(sub, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + println(json) + if let sid = json["sid"] as? String { + self?.sid = sid + + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } + } + } + } + + func handlePollingResponse(str:String) { + // TODO add polling + } + + func parseWebSocketMessage(message:AnyObject?) { + if !(message is String) { + return + } + + var strMessage = RegexMutable(message as String) + + // We should upgrade + if strMessage == "3probe" { + self.upgradeTransport() + return + } + + let type = strMessage["(\\d)"].matches()[0] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + return + } + } + + func probeWebSocket() { + if self.websocketConnected { + self.ws?.send("2probe") + } + } + + func sendPing() { + if self.websocketConnected { + self.ws?.send(PacketType.PING.rawValue) + } + } + + // Starts the ping timer + private func startPingTimer() { + if self.pingInterval == nil { + return + } + + dispatch_async(dispatch_get_main_queue()) { + self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, + selector: Selector("sendPing"), userInfo: nil, repeats: true) + } + } + + private func upgradeTransport() { + if self.websocketConnected { + self.ws?.send(PacketType.UPGRADE.rawValue) + } + } + + // Called when a message is recieved + func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { + // println(message) + + self.parseWebSocketMessage(message) + } + + // Called when the socket is opened + func webSocketDidOpen(webSocket:SRWebSocket!) { + println("socket opened") + self.startPingTimer() + self.websocketConnected = true + self.probeWebSocket() + } + + // Called when the socket is closed + func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + println("socket closed") + self.pingTimer?.invalidate() + self.websocketConnected = false + } + + // Called when an error occurs. + func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { + self.pingTimer?.invalidate() + self.websocketConnected = false + } +} \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index b835092..7cd4423 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -25,6 +25,7 @@ import Foundation class SocketIOClient: NSObject, SRWebSocketDelegate { + let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -39,7 +40,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { private var lastSocketMessage:SocketEvent? private var paramConnect = false private var pingTimer:NSTimer! - private var secure = false + private var _secure = false var closed = false var connected = false var connecting = false @@ -49,13 +50,16 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var reconnecting = false var reconnectAttempts = -1 var reconnectWait = 10 + var secure:Bool { + return self._secure + } var sid:String? init(socketURL:String, opts:[String: AnyObject]? = nil) { var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { - self.secure = true + self._secure = true } mutURL = mutURL["http://"] ~= "" @@ -81,6 +85,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.nsp = nsp } } + + super.init() + + self.engine = SocketEngine(client: self) } // Closes the socket @@ -144,7 +152,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } private func createConnectURL() -> String { - if self.secure { + if self._secure { return "wss://\(self.socketURL)/socket.io/?transport=websocket" } else { return "ws://\(self.socketURL)/socket.io/?transport=websocket" @@ -313,7 +321,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - private func joinNamespace() { + func joinNamespace() { if self.nsp != nil { self.io?.send("40/\(self.nsp!)") } @@ -891,4 +899,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.tryReconnect(triesLeft: self.reconnectAttempts) } } -} + + func testEngine() { + self.engine.open() + } +} \ No newline at end of file From 5cc42be7091b2fbabc1ac27dff85864a2f5c5b2f Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 21:38:11 -0500 Subject: [PATCH 15/64] start moving websocket stuff to socketengine --- SwiftIO/SocketEngine.swift | 16 ++++++---- SwiftIO/SocketIOClient.swift | 59 ++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index f9198be..007f5e7 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -56,16 +56,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func open(opts:[String: AnyObject]? = nil) { - var url:String + var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String if self.client.secure { - url = "\(self.client.socketURL)/socket.io/?transport=" urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" } else { - url = "\(self.client.socketURL)/socket.io/?transport=" urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } @@ -109,12 +107,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO add polling } - func parseWebSocketMessage(message:AnyObject?) { - if !(message is String) { + func parseWebSocketMessage(var message:AnyObject?) { + if let data = message as? NSData { + // Strip off message type + self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) return } - var strMessage = RegexMutable(message as String) + var message = message as String + var strMessage = RegexMutable(message) // We should upgrade if strMessage == "3probe" { @@ -128,6 +129,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO Handle other packets return } + + message.removeAtIndex(message.startIndex) + self.client.parseSocketMessage(message) } func probeWebSocket() { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 7cd4423..77ff582 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -87,7 +87,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } super.init() - + self.engine = SocketEngine(client: self) } @@ -506,7 +506,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses messages recieved - private func parseSocketMessage(message:AnyObject?) { + func parseSocketMessage(message:AnyObject?) { if message == nil { return } @@ -515,41 +515,39 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let stringMessage = message as? String { // Check for successful namepsace connect + + if stringMessage == "0" { + // connected + self.closed = false + self.connecting = false + self.reconnecting = false + self.connected = true + + if self.nsp != nil { + // Join namespace + self.joinNamespace() + return + } + + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.handleEvent("connect", data: nil) + } if self.nsp != nil { - if stringMessage == "40/\(self.nsp!)" { + if stringMessage == "0/\(self.nsp!)" { self.handleEvent("connect", data: nil) return } } - /** - Begin check for socket info frame - **/ - var mutMessage = RegexMutable(stringMessage) - var setup:String! - let messageData = mutMessage["(\\d*)(\\{.*\\})?"].groups() - if messageData != nil && messageData[1] == "0" { - setup = messageData[2] - let data = setup.dataUsingEncoding(NSUTF8StringEncoding)! - var jsonError:NSError? - - if let json:AnyObject? = NSJSONSerialization.JSONObjectWithData(data, - options: nil, error: &jsonError) { - self.sid = json!["sid"] as? String - self.startPingTimer(interval: (json!["pingInterval"] as Int) / 1000) - return - } - } - /** - End check for socket info frame - **/ + var mutMessage = RegexMutable(stringMessage) /** Begin check for message **/ let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - if messageGroups[1].hasPrefix("42") { + if messageGroups[1].hasPrefix("2") { var mesNum = messageGroups[1] var ackNum:String var namespace:String? @@ -558,7 +556,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if messageGroups[3] != "" { ackNum = messageGroups[3] } else { - let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 2)) + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) mesNum.replaceRange(range, with: "") ackNum = mesNum } @@ -581,6 +579,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { data = messageInternals[2] } + println(data) // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { @@ -623,7 +622,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } return } - } else if messageGroups[1].hasPrefix("43") { + } else if messageGroups[1].hasPrefix("3") { let arr = Array(messageGroups[1]) var ackNum:String let nsp = messageGroups[2] @@ -677,7 +676,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - if binaryGroup[1].hasPrefix("45") { + if binaryGroup[1].hasPrefix("5") { // println(binaryGroup) var ackNum:String var event:String @@ -718,9 +717,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } self.lastSocketMessage = mes - } else if binaryGroup[1].hasPrefix("46") { + } else if binaryGroup[1].hasPrefix("6") { let messageType = RegexMutable(binaryGroup[1]) - let numberOfPlaceholders = (messageType["46"] ~= "") as String + let numberOfPlaceholders = (messageType["6"] ~= "") as String var ackNum:String var nsp:String From 7e0549611fdf63ab1f7a7dbc5f01dcc90f5b87dd Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 22:13:28 -0500 Subject: [PATCH 16/64] move more things to socketengine. reconnecting broken --- SwiftIO/SocketEngine.swift | 55 +++++++++++++++++++++++-- SwiftIO/SocketEvent.swift | 27 ++++++------ SwiftIO/SocketIOClient.swift | 80 +++++++----------------------------- 3 files changed, 80 insertions(+), 82 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 007f5e7..58bdc3d 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -55,7 +55,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.client = client } - func open(opts:[String: AnyObject]? = nil) { + func close() { + if websocketConnected { + self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) + self.ws?.close() + } + } + + private func createURLs(params:[String: AnyObject]? = nil) -> (NSURL, NSURL) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String @@ -68,7 +75,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { urlWebSocket = "ws://" + url + "websocket" } - let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling)!) + if params != nil { + for (key, value) in params! { + let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + urlPolling += "&\(keyEsc)=" + urlWebSocket += "&\(keyEsc)=" + + if value is String { + let valueEsc = (value as String).stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + urlPolling += "\(valueEsc)" + urlWebSocket += "\(valueEsc)" + } else { + urlPolling += "\(value)" + urlWebSocket += "\(value)" + } + } + } + + return (NSURL(string: urlPolling)!, NSURL(string: urlWebSocket)!) + } + + func open(opts:[String: AnyObject]? = nil) { + let (urlPolling, urlWebSocket) = self.createURLs(params: opts) + let reqPolling = NSURLRequest(URL: urlPolling) NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in var err:NSError? @@ -87,7 +118,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let sid = json["sid"] as? String { self?.sid = sid - self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws = SRWebSocket(URL: urlWebSocket.URLByAppendingPathComponent("&sid=\(self!.sid)")) self?.ws?.delegate = self self?.ws?.open() @@ -140,6 +171,18 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + func send(msg:AnyObject) { + if self.websocketConnected { + if !(msg is NSData) { + self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") + } else { + self.ws?.send(msg) + } + } else { + // TODO handle polling + } + } + func sendPing() { if self.websocketConnected { self.ws?.send(PacketType.PING.rawValue) @@ -184,11 +227,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { println("socket closed") self.pingTimer?.invalidate() self.websocketConnected = false + + // Temp + self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) } // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.pingTimer?.invalidate() self.websocketConnected = false + + // Temp + self.client.webSocket(webSocket, didFailWithError: error) } } \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 26d8499..7f5f133 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -74,29 +74,29 @@ class SocketEvent { if !hasBinary { if nsp == nil { if ack == nil { - message = "42[\"\(event)\"" + message = "2[\"\(event)\"" } else { - message = "42\(ack!)[\"\(event)\"" + message = "2\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "42/\(nsp!),[\"\(event)\"" + message = "2/\(nsp!),[\"\(event)\"" } else { - message = "42/\(nsp!),\(ack!)[\"\(event)\"" + message = "2/\(nsp!),\(ack!)[\"\(event)\"" } } } else { if nsp == nil { if ack == nil { - message = "45\(datas)-[\"\(event)\"" + message = "5\(datas)-[\"\(event)\"" } else { - message = "45\(datas)-\(ack!)[\"\(event)\"" + message = "5\(datas)-\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + message = "5\(datas)-/\(nsp!),[\"\(event)\"" } else { - message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"" + message = "5\(datas)-/\(nsp!),\(ack!)[\"\(event)\"" } } } @@ -110,15 +110,15 @@ class SocketEvent { if ackType == 3 { if nsp == "/" { - msg = "43\(ack)[" + msg = "3\(ack)[" } else { - msg = "43/\(nsp),\(ack)[" + msg = "3/\(nsp),\(ack)[" } } else { if nsp == "/" { - msg = "46\(binary)-\(ack)[" + msg = "6\(binary)-\(ack)[" } else { - msg = "46\(binary)-/\(nsp),\(ack)[" + msg = "6\(binary)-/\(nsp),\(ack)[" } } @@ -159,6 +159,7 @@ class SocketEvent { if message != "" { message.removeAtIndex(message.endIndex.predecessor()) } + return message + "]" } @@ -251,4 +252,4 @@ class SocketEvent { return false } -} +} \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 77ff582..4f38b9e 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient: NSObject, SRWebSocketDelegate { +class SocketIOClient: NSObject { let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), @@ -44,7 +44,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var closed = false var connected = false var connecting = false - var io:SRWebSocket? var nsp:String? var reconnects = true var reconnecting = false @@ -97,49 +96,20 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = true self.connecting = false self.connected = false - self.io?.send("41") - self.io?.close() + self.engine?.close() } // Connects to the server func connect() { - self.connectWithURL(self.createConnectURL()) + self.engine.open() } // Connect to the server using params func connectWithParams(params:[String: AnyObject]) { self.params = params self.paramConnect = true - var endpoint = self.createConnectURL() - for (key, value) in params { - let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( - NSCharacterSet.URLHostAllowedCharacterSet())! - endpoint += "&\(keyEsc)=" - - if value is String { - let valueEsc = (value as String).stringByAddingPercentEncodingWithAllowedCharacters( - NSCharacterSet.URLHostAllowedCharacterSet())! - endpoint += "\(valueEsc)" - } else { - endpoint += "\(value)" - } - } - - self.connectWithURL(endpoint) - } - - private func connectWithURL(url:String) { - if self.closed { - println("Warning: This socket was previvously closed. Reopening could be dangerous. Be careful.") - } - - self.connecting = true - self.closed = false - - self.io = SRWebSocket(URL: NSURL(string: url)) - self.io?.delegate = self - self.io?.open() + self.engine.open(opts: params) } // Creates a binary message, ready for sending @@ -151,14 +121,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return mutData } - private func createConnectURL() -> String { - if self._secure { - return "wss://\(self.socketURL)/socket.io/?transport=websocket" - } else { - return "ws://\(self.socketURL)/socket.io/?transport=websocket" - } - } - // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -215,9 +177,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) + self.engine?.send(str) for data in emitDatas { - self.io?.send(data) + self.engine?.send(data) } } else { if !ack { @@ -228,7 +190,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withDatas: 0, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) + self.engine?.send(str) } } @@ -251,7 +213,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withAckType: 3, withNsp: self!.nsp!) } - self?.io?.send(str) + self?.engine?.send(str) } else { if self?.nsp == nil { str = SocketEvent.createAck(ack, withArgs: items, @@ -261,9 +223,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - self?.io?.send(str) + self?.engine?.send(str) for data in emitDatas { - self?.io?.send(data) + self?.engine?.send(data) } } } @@ -321,9 +283,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } + // Should be removed and moved to SocketEngine func joinNamespace() { if self.nsp != nil { - self.io?.send("40/\(self.nsp!)") + self.engine?.send("0/\(self.nsp!)") } } @@ -579,7 +542,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { data = messageInternals[2] } - println(data) // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { @@ -632,7 +594,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } if nsp == "" { - ackNum = String(arr[2...arr.count-1]) + ackNum = String(arr[1...arr.count-1]) } else { ackNum = messageGroups[3] } @@ -694,7 +656,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { ackNum = "" } - numberOfPlaceholders = (messageType["45"] ~= "") as String + numberOfPlaceholders = (messageType["5"] ~= "") as String event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String mutMessageObject = RegexMutable(binaryGroup[5]) @@ -787,20 +749,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - func sendPing() { - if self.connected { - self.io?.send("2") - } - } - - // Starts the ping timer - private func startPingTimer(#interval:Int) { - dispatch_async(dispatch_get_main_queue()) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) - } - } - // We lost connection and should attempt to reestablish private func tryReconnect(var #triesLeft:Int) { if triesLeft != -1 && triesLeft <= 0 { From 16fd0436328003a73d266c7cdc6fd313cbd1e10a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 22:39:37 -0500 Subject: [PATCH 17/64] fix reconnect --- SwiftIO/SocketEngine.swift | 11 +++++++---- SwiftIO/SocketIOClient.swift | 6 +----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 58bdc3d..a3c3a90 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -103,18 +103,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in var err:NSError? - - if self == nil || err != nil || data == nil { + if self == nil { + return + } else if err != nil || data == nil { println("Error") println(err) - exit(1) + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return } let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) if let json = NSJSONSerialization.JSONObjectWithData(sub, options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - println(json) if let sid = json["sid"] as? String { self?.sid = sid diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 4f38b9e..ff3f622 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -750,7 +750,7 @@ class SocketIOClient: NSObject { } // We lost connection and should attempt to reestablish - private func tryReconnect(var #triesLeft:Int) { + func tryReconnect(var #triesLeft:Int) { if triesLeft != -1 && triesLeft <= 0 { self.connecting = false self.reconnects = false @@ -846,8 +846,4 @@ class SocketIOClient: NSObject { self.tryReconnect(triesLeft: self.reconnectAttempts) } } - - func testEngine() { - self.engine.open() - } } \ No newline at end of file From 6e4aa4eee40c16c9540d40295edc529e9cb61359 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 09:12:51 -0500 Subject: [PATCH 18/64] work on polling heartbeat --- SwiftIO/SocketEngine.swift | 156 ++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 35 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a3c3a90..ecd6f77 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -36,8 +36,9 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient - private var pingTimer:NSTimer? private let pollingQueue = NSOperationQueue() + private var pingTimer:NSTimer? + private var pollingTimer:NSTimer? private var _polling = true private var _websocket = false private var websocketConnected = false @@ -46,6 +47,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return self._polling } var sid = "" + var urlPolling:String? + var urlWebSocket:String? var websocket:Bool { return self._websocket } @@ -56,16 +59,20 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func close() { + self.pingTimer?.invalidate() + self.pollingTimer?.invalidate() + if websocketConnected { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) self.ws?.close() } } - private func createURLs(params:[String: AnyObject]? = nil) -> (NSURL, NSURL) { + private func createURLs(params:[String: AnyObject]? = nil) -> (String, String) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String + let time = Int(NSDate().timeIntervalSince1970 * 1000) if self.client.secure { urlPolling = "https://" + url + "polling" @@ -94,46 +101,93 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - return (NSURL(string: urlPolling)!, NSURL(string: urlWebSocket)!) + return (urlPolling, urlWebSocket) + } + + func doPoll() { + if self.urlPolling == nil || self.websocket { + return + } + + let time = Int(NSDate().timeIntervalSince1970) + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + if self == nil { + return + } + + if err != nil { + println(err) + return + } + + let str = NSString(data: data, encoding: NSUTF8StringEncoding) + + println(str) + } } func open(opts:[String: AnyObject]? = nil) { let (urlPolling, urlWebSocket) = self.createURLs(params: opts) - let reqPolling = NSURLRequest(URL: urlPolling) - NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in - var err:NSError? - if self == nil { - return - } else if err != nil || data == nil { - println("Error") - println(err) - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + self.urlPolling = urlPolling + self.urlWebSocket = urlWebSocket + let time = Int(NSDate().timeIntervalSince1970) + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) + + NSURLConnection.sendAsynchronousRequest(reqPolling, + queue: self.pollingQueue) {[weak self] res, data, err in + var err:NSError? + if self == nil { + return + } else if err != nil || data == nil { + println("Error") + println(err) + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return } - return - } - - let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) - - if let json = NSJSONSerialization.JSONObjectWithData(sub, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - self?.sid = sid + + if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { + var mutString = RegexMutable(dataString) + + let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() + + if parsed.count == 4 { + let length = parsed[1] + let type = parsed[2] + let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - self?.ws = SRWebSocket(URL: urlWebSocket.URLByAppendingPathComponent("&sid=\(self!.sid)")) - self?.ws?.delegate = self - self?.ws?.open() + if type != "0" { + NSLog("Error handshaking") + return + } - } else { - NSLog("Error handshaking") - return + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + if let sid = json["sid"] as? String { + self?.sid = sid + + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + //self?.ws?.open() + + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } + } } - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 - } - } + self?.doPoll() + self?.startPingTimer() + } } } @@ -141,7 +195,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO add polling } - func parseWebSocketMessage(var message:AnyObject?) { + func parseWebSocketMessage(message:AnyObject?) { if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -154,6 +208,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // We should upgrade if strMessage == "3probe" { self.upgradeTransport() + self.pollingTimer?.invalidate() return } @@ -182,22 +237,51 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.ws?.send(msg) } } else { - // TODO handle polling + self.sendPollMessage(msg) } } func sendPing() { - if self.websocketConnected { + if self.websocket { self.ws?.send(PacketType.PING.rawValue) + } else { + let time = Int(NSDate().timeIntervalSince1970) + var req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + let postStr = "1:\(PacketType.PING.rawValue)" + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + let postLength = "\(postData.length)" + + req.HTTPMethod = "POST" + req.setValue(postLength, forHTTPHeaderField: "Content-Length") + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + req.HTTPBody = postData + + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + if err != nil { + println(err) + self?.pingTimer?.invalidate() + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return + } + + self?.doPoll() + } } } + func sendPollMessage(msg:AnyObject) { + + } + // Starts the ping timer private func startPingTimer() { if self.pingInterval == nil { return } + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, selector: Selector("sendPing"), userInfo: nil, repeats: true) @@ -206,6 +290,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + self._websocket = true + self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) } } From 7f22dc8f422f905b7d4cef77bc6b41016cee176f Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 10:04:26 -0500 Subject: [PATCH 19/64] send connect event on polling connect --- SwiftIO/SocketEngine.swift | 46 +++++++++++++++++++++--------------- SwiftIO/SocketIOClient.swift | 10 ++++---- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index ecd6f77..1efdfbf 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -72,7 +72,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String - let time = Int(NSDate().timeIntervalSince1970 * 1000) if self.client.secure { urlPolling = "https://" + url + "polling" @@ -115,9 +114,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in if self == nil { return - } - - if err != nil { + } else if err != nil { println(err) return } @@ -125,6 +122,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let str = NSString(data: data, encoding: NSUTF8StringEncoding) println(str) + + // TODO parse packets } } @@ -169,10 +168,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { if let sid = json["sid"] as? String { self?.sid = sid - + self?.client.didConnect() + self?.client.handleEvent("connect", data: nil, isInternalMessage: false) + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - //self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") @@ -208,7 +209,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // We should upgrade if strMessage == "3probe" { self.upgradeTransport() - self.pollingTimer?.invalidate() return } @@ -246,7 +246,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.ws?.send(PacketType.PING.rawValue) } else { let time = Int(NSDate().timeIntervalSince1970) - var req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + var req = NSMutableURLRequest(URL: NSURL(string: + self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) let postStr = "1:\(PacketType.PING.rawValue)" let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! let postLength = "\(postData.length)" @@ -256,17 +257,20 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") req.HTTPBody = postData - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in - if err != nil { - println(err) - self?.pingTimer?.invalidate() - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + NSURLConnection.sendAsynchronousRequest(req, + queue: self.pollingQueue) {[weak self] res, data, err in + if self == nil { + return + } else if err != nil { + println(err) + self?.pingTimer?.invalidate() + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return } - return - } - - self?.doPoll() + + self?.doPoll() } } } @@ -292,6 +296,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self._websocket = true self._polling = false + self.pollingTimer?.invalidate() self.ws?.send(PacketType.UPGRADE.rawValue) } } @@ -306,7 +311,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { println("socket opened") - self.startPingTimer() self.websocketConnected = true self.probeWebSocket() } @@ -316,6 +320,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { println("socket closed") self.pingTimer?.invalidate() self.websocketConnected = false + self._websocket = false + self._polling = true // Temp self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) @@ -325,6 +331,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.pingTimer?.invalidate() self.websocketConnected = false + self._websocket = false + self._polling = true // Temp self.client.webSocket(webSocket, didFailWithError: error) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index ff3f622..3e54dcb 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -39,7 +39,6 @@ class SocketIOClient: NSObject { private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var paramConnect = false - private var pingTimer:NSTimer! private var _secure = false var closed = false var connected = false @@ -92,7 +91,6 @@ class SocketIOClient: NSObject { // Closes the socket func close() { - self.pingTimer?.invalidate() self.closed = true self.connecting = false self.connected = false @@ -121,6 +119,12 @@ class SocketIOClient: NSObject { return mutData } + func didConnect() { + self.connected = true + self.connecting = false + self.reconnecting = false + } + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -821,7 +825,6 @@ class SocketIOClient: NSObject { // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { - self.pingTimer?.invalidate() self.connected = false self.connecting = false if self.closed || !self.reconnects { @@ -835,7 +838,6 @@ class SocketIOClient: NSObject { // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.pingTimer?.invalidate() self.connected = false self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) From 574e9d5f7eae16a5ea252c04aae5c066d7a8c10f Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 13:27:55 -0500 Subject: [PATCH 20/64] start work on parsing polling messages --- SwiftIO/SocketEngine.swift | 129 ++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 1efdfbf..58a3d81 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -24,6 +24,12 @@ import Foundation +extension String { + private var length:Int { + return Array(self).count + } +} + private enum PacketType: String { case OPEN = "0" case CLOSE = "1" @@ -40,6 +46,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private var pingTimer:NSTimer? private var pollingTimer:NSTimer? private var _polling = true + private var wait = false private var _websocket = false private var websocketConnected = false var pingInterval:Int? @@ -62,9 +69,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.pollingTimer?.invalidate() - if websocketConnected { + if self.websocket { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) self.ws?.close() + } else { + // TODO handling polling } } @@ -104,26 +113,45 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func doPoll() { - if self.urlPolling == nil || self.websocket { + if self.urlPolling == nil || self.websocket || self.wait { return } let time = Int(NSDate().timeIntervalSince1970) let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + self.wait = true NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { println(err) + self?.handlePollingFailed() return } - let str = NSString(data: data, encoding: NSUTF8StringEncoding) - - println(str) - - // TODO parse packets + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + // println(str) + var mut = RegexMutable(str) + + let groups = mut["(\\d):(.*)"].groups() + if groups[1] == "" || groups[2] == "" { + return + } + + let type = groups[1] + var mutPart = RegexMutable(groups[0]) + + if type != "2" { + return + } + + mutPart["^2:40"] ~= "" + + self?.parsePollingMessage(mutPart) + self?.wait = false + self?.doPoll() + } } } @@ -141,12 +169,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - println("Error") println(err) - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) - } + self?.handlePollingFailed() return + } if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { @@ -170,10 +196,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.sid = sid self?.client.didConnect() self?.client.handleEvent("connect", data: nil, isInternalMessage: false) - + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - self?.ws?.open() + //self?.ws?.open() } else { NSLog("Error handshaking") @@ -192,11 +218,71 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func handlePollingResponse(str:String) { - // TODO add polling + // A poll failed, try and reconnect + private func handlePollingFailed() { + if !self.client.reconnecting { + self.pingTimer?.invalidate() + self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) + } } - func parseWebSocketMessage(message:AnyObject?) { + // Translatation of engine.io-parser#decodePayload + func parsePollingMessage(str:String) { + if str.length == 1 { + return + } + + // println(str) + + var length = "" + var n = 0 + var msg = "" + + func testLength(length:String, inout n:Int) -> Bool { + if let num = length.toInt() { + n = num + if num != n { + return true + } + } + + return false + } + + for var i = 0, l = str.length; i < l; i++ { + let strArray = Array(str) + let chr = String(strArray[i]) + + if chr != ":" { + length += chr + } else { + if testLength(length, &n) || length == "" { + println("parsing error at testlength") + return + } + + msg = String(strArray[i+1...i+n]) + + if let lengthInt = length.toInt() { + if lengthInt != msg.length { + println("parsing error") + return + } + } + + if msg.length != 0 { + self.parseEngineMessage(msg) + } + + i += n + length = "" + } + } + } + + func parseEngineMessage(message:AnyObject?) { + // println(message) + if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -212,13 +298,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let type = strMessage["(\\d)"].matches()[0] + let type = strMessage["^(\\d)"].groups()?[1] if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets + println(message) return } + // Remove message type message.removeAtIndex(message.startIndex) self.client.parseSocketMessage(message) } @@ -263,10 +351,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } else if err != nil { println(err) - self?.pingTimer?.invalidate() - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) - } + self?.handlePollingFailed() return } @@ -305,7 +390,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseWebSocketMessage(message) + self.parseEngineMessage(message) } // Called when the socket is opened From 3088bd88e6977d01c72c66283a83101c93af82a6 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 14:06:20 -0500 Subject: [PATCH 21/64] handle binary --- SwiftIO/SocketEngine.swift | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 58a3d81..4a26145 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -130,28 +130,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + // println(data) + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - var mut = RegexMutable(str) - let groups = mut["(\\d):(.*)"].groups() - if groups[1] == "" || groups[2] == "" { - return - } - - let type = groups[1] - var mutPart = RegexMutable(groups[0]) - - if type != "2" { - return - } - - mutPart["^2:40"] ~= "" - - self?.parsePollingMessage(mutPart) - self?.wait = false - self?.doPoll() + self?.parsePollingMessage(str) } + + self?.wait = false + self?.doPoll() } } @@ -241,9 +229,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func testLength(length:String, inout n:Int) -> Bool { if let num = length.toInt() { n = num - if num != n { - return true - } + } else { + return true } return false @@ -302,7 +289,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - println(message) + 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) { + self.client.parseSocketMessage(data) + } return } From 2e80b9405db04ccdd9286459016be3ee3fe5ec6c Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 15:13:41 -0500 Subject: [PATCH 22/64] Fixes. Sending doesn't work on polling, namespaces broken --- SwiftIO/SocketEngine.swift | 93 +++++++++++++++++++----------------- SwiftIO/SocketIOClient.swift | 32 ++----------- 2 files changed, 53 insertions(+), 72 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 4a26145..b0afd41 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -44,7 +44,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let pollingQueue = NSOperationQueue() private var pingTimer:NSTimer? - private var pollingTimer:NSTimer? private var _polling = true private var wait = false private var _websocket = false @@ -67,7 +66,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func close() { self.pingTimer?.invalidate() - self.pollingTimer?.invalidate() if self.websocket { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) @@ -165,39 +163,46 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { var mutString = RegexMutable(dataString) - let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() - if parsed.count == 4 { - let length = parsed[1] - let type = parsed[2] - let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - - if type != "0" { - NSLog("Error handshaking") - return - } - - if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - self?.sid = sid - self?.client.didConnect() - self?.client.handleEvent("connect", data: nil, isInternalMessage: false) - - self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - //self?.ws?.open() - - } else { - NSLog("Error handshaking") - return + if parsed.count != 4 { + return + } + + let length = parsed[1] + let type = parsed[2] + let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + if type != "0" { + NSLog("Error handshaking") + return + } + + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + if let sid = json["sid"] as? String { + // println(json) + self?.sid = sid + + if let up = json["upgrades"] as? [String] { + for available in up { + if available == "websocket" { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + } + } } - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 - } - } + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } } self?.doPoll() @@ -293,12 +298,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // binary in base64 string message.removeRange(Range(start: message.startIndex, end: advance(message.startIndex, 2))) + + if let data = NSData(base64EncodedString: message, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + self.client.parseSocketMessage(data) + } + + return } - if let data = NSData(base64EncodedString: message, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - self.client.parseSocketMessage(data) - } + println("Got something idk what to do with") + println(message) return } @@ -314,7 +324,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func send(msg:AnyObject) { - if self.websocketConnected { + if self.websocket { if !(msg is NSData) { self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") } else { @@ -377,7 +387,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self._websocket = true self._polling = false - self.pollingTimer?.invalidate() self.ws?.send(PacketType.UPGRADE.rawValue) } } @@ -391,31 +400,25 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { - println("socket opened") self.websocketConnected = true self.probeWebSocket() } // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { - println("socket closed") - self.pingTimer?.invalidate() self.websocketConnected = false self._websocket = false self._polling = true - // Temp - self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) + self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) } // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.pingTimer?.invalidate() self.websocketConnected = false self._websocket = false self._polling = true - // Temp - self.client.webSocket(webSocket, didFailWithError: error) + self.client.webSocketDidFailWithError(error) } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 3e54dcb..3e18d75 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient: NSObject { +class SocketIOClient { let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), @@ -84,8 +84,6 @@ class SocketIOClient: NSObject { } } - super.init() - self.engine = SocketEngine(client: self) } @@ -120,6 +118,7 @@ class SocketIOClient: NSObject { } func didConnect() { + self.closed = false self.connected = true self.connecting = false self.reconnecting = false @@ -485,10 +484,7 @@ class SocketIOClient: NSObject { if stringMessage == "0" { // connected - self.closed = false - self.connecting = false - self.reconnecting = false - self.connected = true + self.didConnect() if self.nsp != nil { // Join namespace @@ -805,26 +801,8 @@ class SocketIOClient: NSObject { } } - // Called when the socket is opened - func webSocketDidOpen(webSocket:SRWebSocket!) { - self.closed = false - self.connecting = false - self.reconnecting = false - self.connected = true - - if self.nsp != nil { - // Join namespace - self.joinNamespace() - return - } - - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.handleEvent("connect", data: nil) - } - // Called when the socket is closed - func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + func webSocketDidCloseWithCode(code:Int, reason:String!, wasClean:Bool) { self.connected = false self.connecting = false if self.closed || !self.reconnects { @@ -837,7 +815,7 @@ class SocketIOClient: NSObject { } // Called when an error occurs. - func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { + func webSocketDidFailWithError(error:NSError!) { self.connected = false self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) From 2205cfb7d9837e4c30d0761f30137a22480c1843 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 15:52:18 -0500 Subject: [PATCH 23/64] fix EXE_BAD_ACCESS when compiling with -O with swift 1.1 --- SwiftIO/SocketEngine.swift | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index b0afd41..9e6f59e 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -24,6 +24,10 @@ import Foundation +// This is used because in Swift 1.1, turning on -O causes a +// memory access violation in SocketEngine#parseEngineMessage +private var fixSwift:AnyObject? + extension String { private var length:Int { return Array(self).count @@ -194,7 +198,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } } - } else { NSLog("Error handshaking") return @@ -220,7 +223,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } // Translatation of engine.io-parser#decodePayload - func parsePollingMessage(str:String) { + private func parsePollingMessage(str:String) { if str.length == 1 { return } @@ -263,7 +266,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if msg.length != 0 { - self.parseEngineMessage(msg) + fixSwift = msg + self.parseEngineMessage(fixSwift) } i += n @@ -272,7 +276,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func parseEngineMessage(message:AnyObject?) { + private func parseEngineMessage(message:AnyObject?) { // println(message) if let data = message as? NSData { @@ -281,8 +285,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - var message = message as String - var strMessage = RegexMutable(message) + var messageString = message as String + var strMessage = RegexMutable(messageString) // We should upgrade if strMessage == "3probe" { @@ -294,12 +298,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - if message.hasPrefix("b4") { + if messageString.hasPrefix("b4") { // binary in base64 string - message.removeRange(Range(start: message.startIndex, - end: advance(message.startIndex, 2))) + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) - if let data = NSData(base64EncodedString: message, + if let data = NSData(base64EncodedString: messageString, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { self.client.parseSocketMessage(data) } @@ -308,13 +312,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } println("Got something idk what to do with") - println(message) - return + println(messageString) } // Remove message type - message.removeAtIndex(message.startIndex) - self.client.parseSocketMessage(message) + messageString.removeAtIndex(messageString.startIndex) + self.client.parseSocketMessage(messageString) } func probeWebSocket() { From 4925c00c1228f3efa291f47f7a06fca4ae5ecb9d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 18:02:42 -0500 Subject: [PATCH 24/64] work on xhr polling sending --- SwiftIO/SocketEngine.swift | 118 +++++++++++++++++++++++++---------- SwiftIO/SocketIOClient.swift | 26 ++++---- 2 files changed, 98 insertions(+), 46 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 9e6f59e..0237c9c 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -30,7 +30,7 @@ private var fixSwift:AnyObject? extension String { private var length:Int { - return Array(self).count + return countElements(self) } } @@ -79,6 +79,22 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { + if self.websocket { + var byteArray = [UInt8](count: 1, repeatedValue: 0x0) + byteArray[0] = 4 + var 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]? = nil) -> (String, String) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String @@ -120,28 +136,30 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } let time = Int(NSDate().timeIntervalSince1970) - let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + let req = NSURLRequest(URL: + NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) self.wait = true - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in - if self == nil { - return - } else if err != nil { - println(err) - self?.handlePollingFailed() - return - } - - // println(data) - - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { - // println(str) + NSURLConnection.sendAsynchronousRequest(req, + queue: self.pollingQueue) {[weak self] res, data, err in + if self == nil { + return + } else if err != nil { + // println(err) + self?.handlePollingFailed() + return + } - self?.parsePollingMessage(str) - } - - self?.wait = false - self?.doPoll() + // println(data) + + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + // println(str) + + self?.parsePollingMessage(str) + } + + self?.wait = false + self?.doPoll() } } @@ -159,7 +177,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - println(err) + // println(err) self?.handlePollingFailed() return @@ -188,16 +206,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(json) self?.sid = sid - if let up = json["upgrades"] as? [String] { - for available in up { - if available == "websocket" { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() - } - } - } + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + // self?.ws?.open() } else { NSLog("Error handshaking") return @@ -218,6 +230,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed() { if !self.client.reconnecting { self.pingTimer?.invalidate() + self.wait = false + self.client.handleEvent("reconnect", data: "XHR polling timeout", isInternalMessage: true) self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) } } @@ -253,7 +267,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { if testLength(length, &n) || length == "" { println("parsing error at testlength") - return + exit(1) } msg = String(strArray[i+1...i+n]) @@ -261,7 +275,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let lengthInt = length.toInt() { if lengthInt != msg.length { println("parsing error") - return + exit(1) } } @@ -331,7 +345,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if !(msg is NSData) { self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") } else { - self.ws?.send(msg) + let (data, nilString) = self.createBinaryDataForSend(msg as NSData) + self.ws?.send(data!) } } else { self.sendPollMessage(msg) @@ -370,7 +385,42 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPollMessage(msg:AnyObject) { + var postData:NSData + let time = Int(NSDate().timeIntervalSince1970) + var req = NSMutableURLRequest(URL: + NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) + req.HTTPMethod = "POST" + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + + if msg is NSData { + println("sending poll binary") + let (nilData, data) = self.createBinaryDataForSend(msg as NSData) + + let dataLen = countElements(data!) + postData = "\(dataLen):\(data!)".dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + } else if !(msg is String) { + return + } else { + // println(msg) + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + // println(strMsg) + + let postCount = countElements(strMsg) + postData = ("\(postCount):\(strMsg)").dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + } + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") + req.HTTPBody = postData + + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + if err != nil { + println(err) + return + } + } } // Starts the ping timer diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 3e18d75..43f6f26 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -109,13 +109,13 @@ class SocketIOClient { } // Creates a binary message, ready for sending - private class func createBinaryDataForSend(data:NSData) -> NSData { - var byteArray = [UInt8](count: 1, repeatedValue: 0x0) - byteArray[0] = 4 - var mutData = NSMutableData(bytes: &byteArray, length: 1) - mutData.appendData(data) - return mutData - } +// private class func createBinaryDataForSend(data:NSData) -> NSData { +// var byteArray = [UInt8](count: 1, repeatedValue: 0x0) +// byteArray[0] = 4 +// var mutData = NSMutableData(bytes: &byteArray, length: 1) +// mutData.appendData(data) +// return mutData +// } func didConnect() { self.closed = false @@ -317,7 +317,7 @@ class SocketIOClient { for g in 0.. Date: Wed, 4 Mar 2015 18:04:45 -0500 Subject: [PATCH 25/64] remove old method --- SwiftIO/SocketIOClient.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 43f6f26..54d48e5 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -108,15 +108,6 @@ class SocketIOClient { self.engine.open(opts: params) } - // Creates a binary message, ready for sending -// private class func createBinaryDataForSend(data:NSData) -> NSData { -// var byteArray = [UInt8](count: 1, repeatedValue: 0x0) -// byteArray[0] = 4 -// var mutData = NSMutableData(bytes: &byteArray, length: 1) -// mutData.appendData(data) -// return mutData -// } - func didConnect() { self.closed = false self.connected = true From 346edc7b53c93eb279be3d7ed4cad01193173ceb Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 19:52:20 -0500 Subject: [PATCH 26/64] Better handle moving between polling and websockets. Sending binary on polling still bork --- SwiftIO/SocketEngine.swift | 58 +++++++++++++++++++++++++++--------- SwiftIO/SocketIOClient.swift | 10 ++----- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 0237c9c..02aa01c 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -34,6 +34,8 @@ extension String { } } +private typealias ProbeQueue = [() -> Void] + private enum PacketType: String { case OPEN = "0" case CLOSE = "1" @@ -46,9 +48,11 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient - private let pollingQueue = NSOperationQueue() + private let workQueue = NSOperationQueue() private var pingTimer:NSTimer? private var _polling = true + private var probing = false + private var probeWait = ProbeQueue() private var wait = false private var _websocket = false private var websocketConnected = false @@ -141,7 +145,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.wait = true NSURLConnection.sendAsynchronousRequest(req, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { @@ -172,7 +176,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) NSURLConnection.sendAsynchronousRequest(reqPolling, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in var err:NSError? if self == nil { return @@ -209,7 +213,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - // self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") return @@ -340,16 +344,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func send(msg:AnyObject) { - if self.websocket { - if !(msg is NSData) { - self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") - } else { - let (data, nilString) = self.createBinaryDataForSend(msg as NSData) - self.ws?.send(data!) + func send(msg:AnyObject, datas:[NSData]? = nil) { + let _send = {[weak self] (msg:AnyObject, datas:[NSData]?) -> () -> Void in + return { + if self == nil { + return + } + + if self!.websocket { + self?.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") + if datas != nil { + for data in datas! { + let (data, nilString) = self!.createBinaryDataForSend(data) + self?.ws?.send(data!) + } + } + } else { + self?.sendPollMessage(msg) + } } + } + + if self.probing { + self.probeWait.append(_send(msg, datas)) } else { - self.sendPollMessage(msg) + _send(msg, datas)() } } @@ -370,7 +389,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPBody = postData NSURLConnection.sendAsynchronousRequest(req, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { @@ -385,6 +404,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPollMessage(msg:AnyObject) { + // println("Sending: \(msg)") + var postData:NSData let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: @@ -415,7 +436,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") req.HTTPBody = postData - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if err != nil { println(err) return @@ -438,9 +459,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + self.probing = false self._websocket = true self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) + for sender in self.probeWait { + sender() + } + + self.probeWait.removeAll(keepCapacity: false) } } @@ -454,12 +481,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { self.websocketConnected = true + self.probing = true self.probeWebSocket() } // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false + self.probing = false self._websocket = false self._polling = true @@ -471,6 +500,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.websocketConnected = false self._websocket = false self._polling = true + self.probing = false self.client.webSocketDidFailWithError(error) } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 54d48e5..a1e43db 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -171,10 +171,7 @@ class SocketIOClient { hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.engine?.send(str) - for data in emitDatas { - self.engine?.send(data) - } + self.engine?.send(str, datas: emitDatas) } else { if !ack { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, @@ -217,10 +214,7 @@ class SocketIOClient { withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - self?.engine?.send(str) - for data in emitDatas { - self?.engine?.send(data) - } + self?.engine?.send(str, datas: emitDatas) } } } From b295561dcc2d013912e3594c81e4da22467f38b9 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 20:22:39 -0500 Subject: [PATCH 27/64] non-namespace polling seems to work, needs more testing --- SwiftIO/SocketEngine.swift | 64 ++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 02aa01c..0428de4 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -167,6 +167,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + private func flushProbeWait() { + for waiter in self.probeWait { + waiter() + } + + self.probeWait.removeAll(keepCapacity: false) + } + func open(opts:[String: AnyObject]? = nil) { let (urlPolling, urlWebSocket) = self.createURLs(params: opts) @@ -360,7 +368,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } } else { - self?.sendPollMessage(msg) + self?.sendPollMessage(msg, datas: datas) } } } @@ -403,10 +411,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendPollMessage(msg:AnyObject) { + func sendPollMessage(msg:AnyObject, datas:[NSData]?) { // println("Sending: \(msg)") - var postData:NSData + var bDatas:[String]? let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) @@ -414,25 +422,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") - if msg is NSData { - println("sending poll binary") - let (nilData, data) = self.createBinaryDataForSend(msg as NSData) - - let dataLen = countElements(data!) - postData = "\(dataLen):\(data!)".dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - } else if !(msg is String) { - return - } else { - // println(msg) - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" - // println(strMsg) - - let postCount = countElements(strMsg) - postData = ("\(postCount):\(strMsg)").dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! + if datas != nil { + bDatas = [String]() + for data in datas! { + let (nilData, b64Data) = self.createBinaryDataForSend(data) + let dataLen = countElements(b64Data!) + + bDatas!.append("\(dataLen):\(b64Data!)") + } } + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + + let postCount = countElements(strMsg) + var postStr = "\(postCount):\(strMsg)" + + if bDatas != nil { + for data in bDatas! { + postStr += data + } + } + + postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") req.HTTPBody = postData @@ -463,11 +477,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self._websocket = true self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) - for sender in self.probeWait { - sender() - } - - self.probeWait.removeAll(keepCapacity: false) + self.flushProbeWait() } } @@ -491,6 +501,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self._websocket = false self._polling = true + self.flushProbeWait() self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) } @@ -501,7 +512,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self._websocket = false self._polling = true self.probing = false + self.flushProbeWait() - self.client.webSocketDidFailWithError(error) + // self.client.webSocketDidFailWithError(error) } } \ No newline at end of file From 1f1ba605743a93f4e7d9c85cf1c39c4a07cd7c89 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 21:20:52 -0500 Subject: [PATCH 28/64] fix namespaces --- SwiftIO/SocketAckHandler.swift | 2 +- SwiftIO/SocketEngine.swift | 8 ++++---- SwiftIO/SocketIOClient.swift | 35 ++++++++++++++++++++++------------ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index 5a9d909..17a4236 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -24,7 +24,7 @@ import Foundation -typealias AckCallback = (AnyObject?) -> Void +typealias AckCallback = (NSArray?) -> Void class SocketAckHandler { let ackNum:Int! diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 0428de4..27fe4d4 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -221,7 +221,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - self?.ws?.open() + // self?.ws?.open() } else { NSLog("Error handshaking") return @@ -278,8 +278,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { length += chr } else { if testLength(length, &n) || length == "" { - println("parsing error at testlength") - exit(1) + self.handlePollingFailed() + return } msg = String(strArray[i+1...i+n]) @@ -287,7 +287,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let lengthInt = length.toInt() { if lengthInt != msg.length { println("parsing error") - exit(1) + return } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index a1e43db..6b65e41 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -225,7 +225,14 @@ class SocketIOClient { if handler.ackNum != ack { return true } else { - handler.callback?(data) + if data is NSArray { + handler.callback?(data as? NSArray) + } else if data != nil { + handler.callback?([data!]) + } else { + handler.callback?(nil) + } + return false } } @@ -465,23 +472,23 @@ class SocketIOClient { if let stringMessage = message as? String { // Check for successful namepsace connect + if self.nsp != nil { + if stringMessage == "0/\(self.nsp!)" { + self.didConnect() + self.handleEvent("connect", data: nil) + return + } + } if stringMessage == "0" { - // connected - self.didConnect() - if self.nsp != nil { // Join namespace self.joinNamespace() return - } - - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.handleEvent("connect", data: nil) - } - if self.nsp != nil { - if stringMessage == "0/\(self.nsp!)" { + } else { + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.didConnect() self.handleEvent("connect", data: nil) return } @@ -705,10 +712,12 @@ class SocketIOClient { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) if self.lastSocketMessage!.justAck! { + // Should handle ack self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) return } + // Should do event if self.lastSocketMessage!.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) @@ -718,11 +727,13 @@ class SocketIOClient { } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() + // Should handle ack if self.lastSocketMessage!.justAck! { self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) return } + // Should handle ack if self.lastSocketMessage!.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) From 63aed1ce91f97c610bb26c4815a8385b468edec1 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 21:50:23 -0500 Subject: [PATCH 29/64] tweaks --- README.md | 2 +- SwiftIO/SocketEngine.swift | 79 ++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index ce733c7..0c48ca5 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ socket.on("ackEvent") {data, ack in } socket.emitWithAck("ackTest", "test").onAck {data in - println(data) + println(data?[0]) } ack?("Got your event", "dude") diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 27fe4d4..5feae6b 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -49,6 +49,8 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() + private let handleQueue = dispatch_queue_create( + "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var pingTimer:NSTimer? private var _polling = true private var probing = false @@ -134,7 +136,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return (urlPolling, urlWebSocket) } - func doPoll() { + private func doPoll() { if self.urlPolling == nil || self.websocket || self.wait { return } @@ -221,7 +223,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - // self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") return @@ -304,46 +306,47 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func parseEngineMessage(message:AnyObject?) { // println(message) - - if let data = message as? NSData { - // Strip off message type - self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - return - } - - var messageString = message as String - var strMessage = RegexMutable(messageString) - - // We should upgrade - if strMessage == "3probe" { - self.upgradeTransport() - return - } - - let type = strMessage["^(\\d)"].groups()?[1] - - if type != PacketType.MESSAGE.rawValue { - // TODO Handle other packets - if messageString.hasPrefix("b4") { - // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) - - if let data = NSData(base64EncodedString: messageString, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - self.client.parseSocketMessage(data) - } - + dispatch_async(self.handleQueue) {[weak self] in + if let data = message as? NSData { + // Strip off message type + self?.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) return } - println("Got something idk what to do with") - println(messageString) + var messageString = message as String + var strMessage = RegexMutable(messageString) + + // We should upgrade + if strMessage == "3probe" { + self?.upgradeTransport() + return + } + + let type = strMessage["^(\\d)"].groups()?[1] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + if messageString.hasPrefix("b4") { + // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) + + if let data = NSData(base64EncodedString: messageString, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + self?.client.parseSocketMessage(data) + } + + return + } + + // println("Got something idk what to do with") + // println(messageString) + } + + // Remove message type + messageString.removeAtIndex(messageString.startIndex) + self?.client.parseSocketMessage(messageString) } - - // Remove message type - messageString.removeAtIndex(messageString.startIndex) - self.client.parseSocketMessage(messageString) } func probeWebSocket() { From 68a0f8d9acf82c0ebee3207b39bbe4a3a470cff5 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 22:53:57 -0500 Subject: [PATCH 30/64] gotta go fast --- SwiftIO/SocketEngine.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 5feae6b..5c84e85 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -272,7 +272,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return false } - for var i = 0, l = str.length; i < l; i++ { + for var i = 0, l = str.length; i < l; i = i &+ 1 { let strArray = Array(str) let chr = String(strArray[i]) @@ -284,7 +284,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - msg = String(strArray[i+1...i+n]) + msg = String(strArray[i&+1...i&+n]) if let lengthInt = length.toInt() { if lengthInt != msg.length { From 1a63088a5e45606e931b178c47831fb58f5fa2cd Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:02:32 -0500 Subject: [PATCH 31/64] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c48ca5..766a5df 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Socket.IO-Client-Swift ====================== -Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1. +Socket.IO-client for Swift. Supports ws/wss/polling connections and binary. For socket.io 1.0+ and Swift 1.1. For Swift 1.2 use the 1.2 branch. From 543a3c8f91067f1f777b4621422dd7774afaaf91 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:15:11 -0500 Subject: [PATCH 32/64] add toggle for polling only --- SwiftIO/SocketEngine.swift | 14 +++++++++----- SwiftIO/SocketIOClient.swift | 7 ++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 5c84e85..b6cf850 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -51,6 +51,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private let workQueue = NSOperationQueue() private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private var forcePolling = false private var pingTimer:NSTimer? private var _polling = true private var probing = false @@ -70,8 +71,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } var ws:SRWebSocket? - init(client:SocketIOClient) { + init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client + self.forcePolling = forcePolling } func close() { @@ -220,10 +222,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(json) self?.sid = sid - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() + if !self!.forcePolling { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + } } else { NSLog("Error handshaking") return diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 6b65e41..72170f3 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -36,6 +36,7 @@ class SocketIOClient { private lazy var params:[String: AnyObject] = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() private var currentAck = -1 + private var forcePolling = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var paramConnect = false @@ -82,9 +83,13 @@ class SocketIOClient { if let nsp = opts!["nsp"] as? String { self.nsp = nsp } + + if let polling = opts!["forcePolling"] as? Bool { + self.forcePolling = polling + } } - self.engine = SocketEngine(client: self) + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } // Closes the socket From 8312d28196c8fbfe3c2b1917c28b145b2d9e7de7 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:16:09 -0500 Subject: [PATCH 33/64] update READEME.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 766a5df..eaf9169 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ API === Constructor ----------- -`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) +`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values. See example) Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. @@ -41,7 +41,8 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "reconnects": true, // default true "reconnectAttempts": 5, // default -1 (infinite tries) "reconnectWait": 5, // default 10 - "nsp": "swift" // connects to the specified namespace. Default is / + "nsp": "swift", // connects to the specified namespace. Default is / + "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) // Socket Events From a1e3232db0cba2c317cf6d7c93f1212bb1878975 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:16:09 -0500 Subject: [PATCH 34/64] update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 766a5df..eaf9169 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ API === Constructor ----------- -`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) +`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values. See example) Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. @@ -41,7 +41,8 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "reconnects": true, // default true "reconnectAttempts": 5, // default -1 (infinite tries) "reconnectWait": 5, // default 10 - "nsp": "swift" // connects to the specified namespace. Default is / + "nsp": "swift", // connects to the specified namespace. Default is / + "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) // Socket Events From 829992fcef62ab4e5246cc624a11ec7c53d47d5d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:39:11 -0500 Subject: [PATCH 35/64] send disconnect on polling close --- SwiftIO/SocketEngine.swift | 10 +++++----- SwiftIO/SocketIOClient.swift | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index b6cf850..321af2b 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -79,11 +79,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func close() { self.pingTimer?.invalidate() - if self.websocket { - self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) - self.ws?.close() - } else { - // TODO handling polling + self.send(PacketType.CLOSE.rawValue) + self.ws?.close() + + if self.polling { + self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 72170f3..90b494d 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -97,6 +97,7 @@ class SocketIOClient { self.closed = true self.connecting = false self.connected = false + self.reconnecting = false self.engine?.close() } From 296802fddbe6a7e31ac70b84a86b79631fd67cc4 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 11:06:11 -0500 Subject: [PATCH 36/64] better handle closes --- SwiftIO/SocketEngine.swift | 62 +++++++++++++++++++++++------------- SwiftIO/SocketIOClient.swift | 8 +++++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 321af2b..4556848 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -76,11 +76,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.forcePolling = forcePolling } - func close() { + func close(forced:Bool = false) { self.pingTimer?.invalidate() - self.send(PacketType.CLOSE.rawValue) - self.ws?.close() + if !forced { + self.send(PacketType.CLOSE.rawValue) + self.ws?.close() + } else { + self.client.didForceClose() + } if self.polling { self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) @@ -343,6 +347,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + if messageString == PacketType.CLOSE.rawValue { + self?.close(forced: true) + return + } // println("Got something idk what to do with") // println(messageString) } @@ -353,27 +361,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func probeWebSocket() { + private func probeWebSocket() { if self.websocketConnected { self.ws?.send("2probe") } } - func send(msg:AnyObject, datas:[NSData]? = nil) { - let _send = {[weak self] (msg:AnyObject, datas:[NSData]?) -> () -> Void in + func send(msg:String, datas:[NSData]? = nil) { + let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { if self == nil { return } if self!.websocket { - self?.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") - if datas != nil { - for data in datas! { - let (data, nilString) = self!.createBinaryDataForSend(data) - self?.ws?.send(data!) - } - } + self?.sendWebSocketMessage(msg, datas: datas) } else { self?.sendPollMessage(msg, datas: datas) } @@ -393,7 +395,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: NSURL(string: - self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + self.urlPolling! + "&sid=\(self.sid)")!) let postStr = "1:\(PacketType.PING.rawValue)" let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! let postLength = "\(postData.length)" @@ -418,13 +420,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendPollMessage(msg:AnyObject, datas:[NSData]?) { + func sendPollMessage(msg:String, datas:[NSData]?) { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? - let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: - NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) + NSURL(string:self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -439,7 +440,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg)" let postCount = countElements(strMsg) var postStr = "\(postCount):\(strMsg)" @@ -459,12 +460,26 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if err != nil { - println(err) + // println(err) + self?.handlePollingFailed() return } } } + func sendWebSocketMessage(str:String, datas:[NSData]?) { + self.ws?.send("\(PacketType.MESSAGE.rawValue)\(str)") + + if datas != nil { + for data in datas! { + let (data, nilString) = self.createBinaryDataForSend(data) + if data != nil { + self.ws?.send(data!) + } + } + } + } + // Starts the ping timer private func startPingTimer() { if self.pingInterval == nil { @@ -491,7 +506,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseEngineMessage(message) } @@ -506,11 +520,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false self.probing = false - self._websocket = false - self._polling = true self.flushProbeWait() - self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + if self.websocket { + self._websocket = false + self._polling = true + self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + } } // Called when an error occurs. diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 90b494d..8108c1a 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -121,6 +121,14 @@ class SocketIOClient { self.reconnecting = false } + // Server wants us to die + func didForceClose() { + self.closed = true + self.connecting = false + self.connected = false + self.reconnecting = false + } + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. From 5ed2688c9e657d3fde79dc0dce0f85370bdb20dd Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 12:55:09 -0500 Subject: [PATCH 37/64] better handle when the server closes the connection (polling) --- SwiftIO/SocketEngine.swift | 38 ++++++++++++++++++++++++++--------- SwiftIO/SocketIOClient.swift | 39 +++++++++++++++--------------------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 4556848..393cf22 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -85,10 +85,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { self.client.didForceClose() } - - if self.polling { - self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) - } } private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { @@ -147,9 +143,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let time = Int(NSDate().timeIntervalSince1970) let req = NSURLRequest(URL: - NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) self.wait = true NSURLConnection.sendAsynchronousRequest(req, @@ -248,13 +243,38 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // A poll failed, try and reconnect + // A poll failed, tell the client about it + // We check to see if we were closed by the server first private func handlePollingFailed() { if !self.client.reconnecting { self.pingTimer?.invalidate() self.wait = false - self.client.handleEvent("reconnect", data: "XHR polling timeout", isInternalMessage: true) - self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) + + let forced = {() -> Bool in + var err:NSError? + let url = NSURL(string: self.urlPolling! + "&sid=\(self.sid)")! + let req = NSURLRequest(URL: url, cachePolicy: + NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 4) + var resp:NSURLResponse? + let data = NSURLConnection.sendSynchronousRequest(req, returningResponse: &resp, error: &err) + + if data == nil || resp == nil || err != nil { + return false + } else if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) { + if str == "1:61:1" { + return true + } else { + return false + } + } else { + return false + }}() + + if forced { + self.close(forced: true) + } else { + self.client.pollingDidFail() + } } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8108c1a..9dcc88b 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -119,14 +119,17 @@ class SocketIOClient { self.connected = true self.connecting = false self.reconnecting = false + self.handleEvent("connect", data: nil, isInternalMessage: false) } // Server wants us to die func didForceClose() { self.closed = true - self.connecting = false self.connected = false + self.reconnects = false + self.connecting = false self.reconnecting = false + self.handleEvent("disconnect", data: "closed", isInternalMessage: true) } // Sends a message with multiple args @@ -489,7 +492,6 @@ class SocketIOClient { if self.nsp != nil { if stringMessage == "0/\(self.nsp!)" { self.didConnect() - self.handleEvent("connect", data: nil) return } } @@ -503,7 +505,6 @@ class SocketIOClient { // Don't handle as internal because something crazy could happen where // we disconnect before it's handled self.didConnect() - self.handleEvent("connect", data: nil) return } } @@ -758,16 +759,20 @@ class SocketIOClient { } } + // Something happened while polling + func pollingDidFail() { + if !self.reconnecting { + self.handleEvent("reconnect", data: "XHR polling error", isInternalMessage: true) + self.tryReconnect(triesLeft: self.reconnectAttempts) + } + } + // We lost connection and should attempt to reestablish func tryReconnect(var #triesLeft:Int) { self.connected = false if triesLeft != -1 && triesLeft <= 0 { - self.connected = false - self.connecting = false - self.reconnects = false - self.reconnecting = false - self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) + self.didForceClose() return } else if self.connected { self.connecting = false @@ -776,7 +781,7 @@ class SocketIOClient { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) + self.handleEvent("reconnectAttempt", data: triesLeft - 1, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -802,27 +807,15 @@ class SocketIOClient { } } - // Called when a message is recieved - func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - dispatch_async(self.handleQueue) {[weak self] in - if self == nil { - return - } - - self?.parseSocketMessage(message) - } - } - // Called when the socket is closed func webSocketDidCloseWithCode(code:Int, reason:String!, wasClean:Bool) { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: reason, isInternalMessage: true) + self.didForceClose() } else { self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) - } } @@ -832,7 +825,7 @@ class SocketIOClient { self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) + self.didForceClose() } else if !self.reconnecting { self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) From c85a1def873a8755d33c720ef6d6ed41b4e3fc04 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 13:45:25 -0500 Subject: [PATCH 38/64] tell client about failed ws --- SwiftIO/SocketEngine.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 393cf22..c018c1b 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -430,7 +430,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil { - println(err) + // println(err) self?.handlePollingFailed() return } @@ -543,6 +543,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.flushProbeWait() if self.websocket { + self.pingTimer?.invalidate() self._websocket = false self._polling = true self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) @@ -557,6 +558,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self.flushProbeWait() - // self.client.webSocketDidFailWithError(error) + self.client.webSocketDidFailWithError(error) } } \ No newline at end of file From d35934c79d8b07e9dac29f712f1d2003591d6307 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 14:37:00 -0500 Subject: [PATCH 39/64] handle binary data better --- SwiftIO/SocketEngine.swift | 47 +++++++++--------------------------- SwiftIO/SocketIOClient.swift | 40 +++++++++++++++--------------- 2 files changed, 32 insertions(+), 55 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index c018c1b..a306661 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -76,15 +76,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.forcePolling = forcePolling } - func close(forced:Bool = false) { + func close() { self.pingTimer?.invalidate() - - if !forced { - self.send(PacketType.CLOSE.rawValue) - self.ws?.close() - } else { - self.client.didForceClose() - } + self.send(PacketType.CLOSE.rawValue) } private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { @@ -250,31 +244,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.wait = false - let forced = {() -> Bool in - var err:NSError? - let url = NSURL(string: self.urlPolling! + "&sid=\(self.sid)")! - let req = NSURLRequest(URL: url, cachePolicy: - NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 4) - var resp:NSURLResponse? - let data = NSURLConnection.sendSynchronousRequest(req, returningResponse: &resp, error: &err) - - if data == nil || resp == nil || err != nil { - return false - } else if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) { - if str == "1:61:1" { - return true - } else { - return false - } - } else { - return false - }}() - - if forced { - self.close(forced: true) - } else { - self.client.pollingDidFail() - } + self.client.pollingDidFail() } } @@ -361,6 +331,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let data = NSData(base64EncodedString: messageString, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + // println("sending \(data)") self?.client.parseSocketMessage(data) } @@ -368,7 +339,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if messageString == PacketType.CLOSE.rawValue { - self?.close(forced: true) + // do nothing return } // println("Got something idk what to do with") @@ -377,6 +348,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Remove message type messageString.removeAtIndex(messageString.startIndex) + // println("sending \(messageString)") + self?.client.parseSocketMessage(messageString) } } @@ -553,11 +526,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.websocketConnected = false - self._websocket = false self._polling = true self.probing = false self.flushProbeWait() - self.client.webSocketDidFailWithError(error) + if self.websocket { + self.pingTimer?.invalidate() + self.client.webSocketDidFailWithError(error) + } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 9dcc88b..d40d570 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -38,7 +38,7 @@ class SocketIOClient { private var currentAck = -1 private var forcePolling = false private var handlers = [SocketEventHandler]() - private var lastSocketMessage:SocketEvent? + private var waitingData = [SocketEvent]() private var paramConnect = false private var _secure = false var closed = false @@ -620,7 +620,7 @@ class SocketIOClient { // Message is binary if let binary = message as? NSData { - if self.lastSocketMessage == nil { + if self.waitingData.isEmpty { return } @@ -667,7 +667,6 @@ class SocketIOClient { mutMessageObject = RegexMutable(binaryGroup[5]) if namespace == "" && self.nsp != nil { - self.lastSocketMessage = nil return } @@ -684,7 +683,7 @@ class SocketIOClient { placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) } - self.lastSocketMessage = mes + self.waitingData.append(mes) } else if binaryGroup[1].hasPrefix("6") { let messageType = RegexMutable(binaryGroup[1]) let numberOfPlaceholders = (messageType["6"] ~= "") as String @@ -706,8 +705,10 @@ class SocketIOClient { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - self.lastSocketMessage = SocketEvent(event: "", args: placeholdersRemoved, + let event = SocketEvent(event: "", args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) + + self.waitingData.append(event) } /** End check for binary placeholders @@ -717,41 +718,42 @@ class SocketIOClient { // Handles binary data private func parseBinaryData(data:NSData) { - let shouldExecute = self.lastSocketMessage?.addData(data) + let shouldExecute = self.waitingData[0].addData(data) - if shouldExecute != nil && shouldExecute! { - var event = self.lastSocketMessage!.event - var parsedArgs:AnyObject? = SocketIOClient.parseData(self.lastSocketMessage!.args as? String) + if shouldExecute { + let socketEvent = self.waitingData.removeAtIndex(0) + var event = socketEvent.event + var parsedArgs:AnyObject? = SocketIOClient.parseData(socketEvent.args as? String) if let args:AnyObject = parsedArgs { - let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders(args) - if self.lastSocketMessage!.justAck! { + if socketEvent.justAck! { // Should handle ack - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + self.handleAck(socketEvent.ack!, data: filledInArgs) return } // Should do event - if self.lastSocketMessage!.ack != nil { + if socketEvent.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + wantsAck: socketEvent.ack!, withAckType: 6) } else { self.handleEvent(event, data: filledInArgs) } } else { - let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders() // Should handle ack - if self.lastSocketMessage!.justAck! { - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + if socketEvent.justAck! { + self.handleAck(socketEvent.ack!, data: filledInArgs) return } // Should handle ack - if self.lastSocketMessage!.ack != nil { + if socketEvent.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + wantsAck: socketEvent.ack!, withAckType: 6) } else { self.handleEvent(event, data: filledInArgs) } From 59a5e1928c76d442559323d53f0cf0f4f8fb5e37 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 15:16:34 -0500 Subject: [PATCH 40/64] clean up --- SwiftIO/SocketEngine.swift | 50 +++++++++++------------------------- SwiftIO/SocketIOClient.swift | 10 ++++++++ 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a306661..435a49c 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -356,7 +356,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func probeWebSocket() { if self.websocketConnected { - self.ws?.send("2probe") + self.sendWebSocketMessage("probe", withType: PacketType.PING) } } @@ -368,9 +368,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if self!.websocket { - self?.sendWebSocketMessage(msg, datas: datas) + // println("sending ws: \(msg)") + self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { - self?.sendPollMessage(msg, datas: datas) + // println("sending poll: \(msg)") + self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) } } } @@ -383,42 +385,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPing() { + // println("sending ping") + if self.websocket { - self.ws?.send(PacketType.PING.rawValue) + self.sendWebSocketMessage("", withType: PacketType.PING) } else { - let time = Int(NSDate().timeIntervalSince1970) - var req = NSMutableURLRequest(URL: NSURL(string: - self.urlPolling! + "&sid=\(self.sid)")!) - let postStr = "1:\(PacketType.PING.rawValue)" - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - let postLength = "\(postData.length)" - - req.HTTPMethod = "POST" - req.setValue(postLength, forHTTPHeaderField: "Content-Length") - req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") - req.HTTPBody = postData - - NSURLConnection.sendAsynchronousRequest(req, - queue: self.workQueue) {[weak self] res, data, err in - if self == nil { - return - } else if err != nil { - // println(err) - self?.handlePollingFailed() - return - } - - self?.doPoll() - } + self.sendPollMessage("", withType: PacketType.PING) } } - func sendPollMessage(msg:String, datas:[NSData]?) { + private func sendPollMessage(msg:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? var req = NSMutableURLRequest(URL: - NSURL(string:self.urlPolling! + "&sid=\(self.sid)")!) + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -433,8 +414,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg)" - + let strMsg = "\(type.rawValue)\(msg)" let postCount = countElements(strMsg) var postStr = "\(postCount):\(strMsg)" @@ -460,8 +440,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendWebSocketMessage(str:String, datas:[NSData]?) { - self.ws?.send("\(PacketType.MESSAGE.rawValue)\(str)") + private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { + self.ws?.send("\(type.rawValue)\(str)") if datas != nil { for data in datas! { @@ -491,7 +471,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self._websocket = true self._polling = false - self.ws?.send(PacketType.UPGRADE.rawValue) + self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self.flushProbeWait() } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index d40d570..e5390b4 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -103,11 +103,21 @@ class SocketIOClient { // Connects to the server func connect() { + if self.closed { + println("Warning! This socket was previously closed. This might be dangerous!") + self.closed = false + } + self.engine.open() } // Connect to the server using params func connectWithParams(params:[String: AnyObject]) { + if self.closed { + println("Warning! This socket was previously closed. This might be dangerous!") + self.closed = false + } + self.params = params self.paramConnect = true From 166b2b515ba82ca326f14b5c2cc8d4083d180ac5 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 15:19:17 -0500 Subject: [PATCH 41/64] make sure we're polling --- SwiftIO/SocketEngine.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 435a49c..6bd66e0 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -437,6 +437,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.handlePollingFailed() return } + + self?.doPoll() } } From 851ac7635aca9aa2606f676841654aa069782e24 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 22:06:17 -0500 Subject: [PATCH 42/64] Fix connection issues, binary data in nested data structures, some race conditions, reconnect needs to be redone --- SwiftIO/SocketEngine.swift | 263 +++++++++++++++++++++-------------- SwiftIO/SocketIOClient.swift | 64 ++++----- 2 files changed, 188 insertions(+), 139 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 6bd66e0..d44f3a2 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -34,7 +34,7 @@ extension String { } } -private typealias ProbeQueue = [() -> Void] +private typealias PollWaitQueue = [() -> Void] private enum PacketType: String { case OPEN = "0" @@ -49,16 +49,21 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() + private let emitQueue = dispatch_queue_create( + "emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var forcePolling = false private var pingTimer:NSTimer? + private var postWait = [String]() private var _polling = true private var probing = false - private var probeWait = ProbeQueue() - private var wait = false + private var probeWait = PollWaitQueue() + private var waitingForPoll = false + private var waitingForPost = false private var _websocket = false private var websocketConnected = false + var connected = false var pingInterval:Int? var polling:Bool { return self._polling @@ -133,21 +138,22 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func doPoll() { - if self.urlPolling == nil || self.websocket || self.wait { + if self.urlPolling == nil || self.websocket || self.waitingForPoll || !self.connected { return } let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - self.wait = true + self.waitingForPoll = true NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { - // println(err) - self?.handlePollingFailed() + if self!.polling { + self?.handlePollingFailed(err) + } return } @@ -156,29 +162,91 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - self?.parsePollingMessage(str) + dispatch_async(self?.handleQueue) {[weak self] in + self?.parsePollingMessage(str) + return + } } - self?.wait = false + self?.waitingForPoll = false self?.doPoll() } } private func flushProbeWait() { - for waiter in self.probeWait { - waiter() + // println("flushing probe wait") + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + for waiter in self!.probeWait { + waiter() + } + + self?.probeWait.removeAll(keepCapacity: false) + } + } + + private func flushWaitingForPost() { + if self.postWait.count == 0 || !self.connected || !self.polling { + return } - self.probeWait.removeAll(keepCapacity: false) + let postStr = self.postWait.reduce("") {$0 + $1} + assert(self.postWait.count != 0) + self.postWait.removeAll(keepCapacity: true) + + var req = NSMutableURLRequest(URL: + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + + req.HTTPMethod = "POST" + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") + req.HTTPBody = postData + + self.waitingForPost = true + NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in + if err != nil { + if self!.polling { + self?.handlePollingFailed(err) + } + return + } + + self?.flushWaitingForPost() + self?.waitingForPost = false + self?.doPoll() + } + } + + // A poll failed, tell the client about it + // We check to see if we were closed by the server first + private func handlePollingFailed(reason:NSError?) { + if !self.client.reconnecting { + self.connected = false + self.pingTimer?.invalidate() + self.waitingForPoll = false + self.waitingForPost = false + self.client.pollingDidFail(reason) + } } func open(opts:[String: AnyObject]? = nil) { + if self.waitingForPost || self.waitingForPoll || self.websocket || self.connected { + assert(false, "We're in a bad state, this shouldn't happen.") + } + let (urlPolling, urlWebSocket) = self.createURLs(params: opts) self.urlPolling = urlPolling self.urlWebSocket = urlWebSocket - let time = Int(NSDate().timeIntervalSince1970) - let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.workQueue) {[weak self] res, data, err in @@ -186,10 +254,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - // println(err) - self?.handlePollingFailed() + if self!.polling { + self?.handlePollingFailed(err) + } return - } if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { @@ -209,6 +277,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + self?.connected = true + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { if let sid = json["sid"] as? String { @@ -237,17 +307,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // A poll failed, tell the client about it - // We check to see if we were closed by the server first - private func handlePollingFailed() { - if !self.client.reconnecting { - self.pingTimer?.invalidate() - self.wait = false - - self.client.pollingDidFail() - } - } - // Translatation of engine.io-parser#decodePayload private func parsePollingMessage(str:String) { if str.length == 1 { @@ -277,8 +336,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if chr != ":" { length += chr } else { - if testLength(length, &n) || length == "" { - self.handlePollingFailed() + if length == "" || testLength(length, &n) { + self.handlePollingFailed(nil) return } @@ -304,54 +363,52 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func parseEngineMessage(message:AnyObject?) { // println(message) - dispatch_async(self.handleQueue) {[weak self] in - if let data = message as? NSData { - // Strip off message type - self?.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - return - } - - var messageString = message as String - var strMessage = RegexMutable(messageString) - - // We should upgrade - if strMessage == "3probe" { - self?.upgradeTransport() - return - } - - let type = strMessage["^(\\d)"].groups()?[1] - - if type != PacketType.MESSAGE.rawValue { - // TODO Handle other packets - if messageString.hasPrefix("b4") { - // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) - - if let data = NSData(base64EncodedString: messageString, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - // println("sending \(data)") - self?.client.parseSocketMessage(data) - } - - return + if let data = message as? NSData { + // Strip off message type + self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + return + } + + var messageString = message as String + var strMessage = RegexMutable(messageString) + + // We should upgrade + if strMessage == "3probe" { + self.upgradeTransport() + return + } + + let type = strMessage["^(\\d)"].groups()?[1] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + if messageString.hasPrefix("b4") { + // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) + + if let data = NSData(base64EncodedString: messageString, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + // println("sending \(data)") + self.client.parseSocketMessage(data) } - if messageString == PacketType.CLOSE.rawValue { - // do nothing - return - } - // println("Got something idk what to do with") - // println(messageString) + return } - // Remove message type - messageString.removeAtIndex(messageString.startIndex) - // println("sending \(messageString)") - - self?.client.parseSocketMessage(messageString) + if messageString == PacketType.CLOSE.rawValue { + // do nothing + return + } + // println("Got something idk what to do with") + // println(messageString) } + + // Remove message type + messageString.removeAtIndex(messageString.startIndex) + // println("sending \(messageString)") + + self.client.parseSocketMessage(messageString) } private func probeWebSocket() { @@ -363,24 +420,30 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func send(msg:String, datas:[NSData]? = nil) { let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { - if self == nil { + if self == nil || !self!.connected { return } if self!.websocket { - // println("sending ws: \(msg)") + // println("sending ws: \(msg):\(datas)") self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { - // println("sending poll: \(msg)") + // println("sending poll: \(msg):\(datas)") self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) } } } - if self.probing { - self.probeWait.append(_send(msg, datas)) - } else { - _send(msg, datas)() + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + if self!.probing { + self?.probeWait.append(_send(msg, datas)) + } else { + _send(msg, datas)() + } } } @@ -398,11 +461,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? - var req = NSMutableURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - - req.HTTPMethod = "POST" - req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") if datas != nil { bDatas = [String]() @@ -424,21 +482,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! + self.postWait.append(postStr) - - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - req.HTTPBody = postData - - NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in - if err != nil { - // println(err) - self?.handlePollingFailed() - return - } - - self?.doPoll() + if waitingForPost { + self.doPoll() + return + } else { + self.flushWaitingForPost() } } @@ -472,6 +522,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self.probing = false self._websocket = true + self.waitingForPoll = false self._polling = false self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self.flushProbeWait() @@ -481,7 +532,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseEngineMessage(message) + + dispatch_async(self.handleQueue) {[weak self] in + self?.parseEngineMessage(message) + return + } } // Called when the socket is opened @@ -495,13 +550,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false self.probing = false - self.flushProbeWait() if self.websocket { self.pingTimer?.invalidate() + self.connected = false self._websocket = false self._polling = true self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + } else { + self.flushProbeWait() } } @@ -510,11 +567,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.websocketConnected = false self._polling = true self.probing = false - self.flushProbeWait() if self.websocket { self.pingTimer?.invalidate() + self.connected = false self.client.webSocketDidFailWithError(error) + } else { + self.flushProbeWait() } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index e5390b4..c761a39 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -151,11 +151,8 @@ class SocketIOClient { } dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args) + return } } @@ -169,11 +166,8 @@ class SocketIOClient { self.ackHandlers.append(ackHandler) dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args, ack: true) + return } return ackHandler @@ -324,29 +318,27 @@ class SocketIOClient { } // Parse an NSArray looking for binary data - private class func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { + private class func parseArray(arr:NSArray, var currentPlaceholder:Int) -> (NSArray, Bool, [NSData]) { var replacementArr = [AnyObject](count: arr.count, repeatedValue: 1) var hasBinary = false var arrayDatas = [NSData]() - if placeholders == -1 { - placeholders = 0 - } - for g in 0.. ([AnyObject], Bool, [NSData]) { var items = [AnyObject](count: args.count, repeatedValue: 1) - var numberOfPlaceholders = -1 + var currentPlaceholder = -1 var hasBinary = false var emitDatas = [NSData]() @@ -401,9 +394,9 @@ class SocketIOClient { if let dict = args[i] as? NSDictionary { // Check for binary data let (newDict, hadBinary, binaryDatas) = SocketIOClient.parseNSDictionary(dict, - placeholders: numberOfPlaceholders) + currentPlaceholder: currentPlaceholder) if hadBinary { - numberOfPlaceholders = binaryDatas.count + currentPlaceholder += binaryDatas.count emitDatas.extend(binaryDatas) hasBinary = true @@ -414,11 +407,11 @@ class SocketIOClient { } else if let arr = args[i] as? NSArray { // arg is array, check for binary let (replace, hadData, newDatas) = SocketIOClient.parseArray(arr, - placeholders: numberOfPlaceholders) + currentPlaceholder: currentPlaceholder) if hadData { hasBinary = true - numberOfPlaceholders += emitDatas.count + currentPlaceholder += newDatas.count for data in newDatas { emitDatas.append(data) @@ -432,8 +425,8 @@ class SocketIOClient { // args is just binary hasBinary = true - numberOfPlaceholders++ - items[i] = ["_placeholder": true, "num": numberOfPlaceholders] + currentPlaceholder++ + items[i] = ["_placeholder": true, "num": currentPlaceholder] emitDatas.append(binaryData) } else { items[i] = args[i] @@ -444,39 +437,36 @@ class SocketIOClient { } // Parses a NSDictionary, looking for NSData objects - private class func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { + private class func parseNSDictionary(dict:NSDictionary, var currentPlaceholder:Int) -> (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false - if placeholders == -1 { - placeholders = 0 - } var returnDatas = [NSData]() for (key, value) in dict { if let binaryData = value as? NSData { + currentPlaceholder++ hasBinary = true - returnDatas.append(binaryData) - returnDict[key as String] = ["_placeholder": true, "num": placeholders++] + returnDict[key as String] = ["_placeholder": true, "num": currentPlaceholder++] } else if let arr = value as? NSArray { - let (replace, hadBinary, arrDatas) = self.parseArray(arr, placeholders: placeholders) + let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) if hadBinary { hasBinary = true returnDict[key as String] = replace - placeholders += arrDatas.count + currentPlaceholder += arrDatas.count returnDatas.extend(arrDatas) } else { returnDict[key as String] = arr } } else if let dict = value as? NSDictionary { // Recursive - let (nestDict, hadBinary, nestDatas) = self.parseNSDictionary(dict, placeholders: placeholders) + let (nestDict, hadBinary, nestDatas) = self.parseNSDictionary(dict, currentPlaceholder: currentPlaceholder) if hadBinary { hasBinary = true returnDict[key as String] = nestDict - placeholders += nestDatas.count + currentPlaceholder += nestDatas.count returnDatas.extend(nestDatas) } else { returnDict[key as String] = dict @@ -772,9 +762,9 @@ class SocketIOClient { } // Something happened while polling - func pollingDidFail() { + func pollingDidFail(err:NSError?) { if !self.reconnecting { - self.handleEvent("reconnect", data: "XHR polling error", isInternalMessage: true) + self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } From 44bff82a43260e6285056e4069999ae598a7bf44 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 12:13:00 -0500 Subject: [PATCH 43/64] better reconnects --- SwiftIO/SocketEngine.swift | 2 -- SwiftIO/SocketIOClient.swift | 52 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index d44f3a2..cedb457 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -194,7 +194,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } let postStr = self.postWait.reduce("") {$0 + $1} - assert(self.postWait.count != 0) self.postWait.removeAll(keepCapacity: true) var req = NSMutableURLRequest(URL: @@ -221,7 +220,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false - self?.doPoll() } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index c761a39..516ee09 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -33,21 +33,23 @@ class SocketIOClient { DISPATCH_QUEUE_SERIAL) let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) - private lazy var params:[String: AnyObject] = [String: AnyObject]() + let reconnectAttempts:Int! + private lazy var params = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() private var currentAck = -1 + private var currentReconnectAttempt = 0 private var forcePolling = false private var handlers = [SocketEventHandler]() private var waitingData = [SocketEvent]() private var paramConnect = false private var _secure = false + private var reconnectTimer:NSTimer? var closed = false var connected = false var connecting = false var nsp:String? var reconnects = true var reconnecting = false - var reconnectAttempts = -1 var reconnectWait = 10 var secure:Bool { return self._secure @@ -65,6 +67,7 @@ class SocketIOClient { mutURL = mutURL["https://"] ~= "" self.socketURL = mutURL + self.reconnectAttempts = -1 // Set options if opts != nil { @@ -129,6 +132,9 @@ class SocketIOClient { self.connected = true self.connecting = false self.reconnecting = false + self.currentReconnectAttempt = 0 + self.reconnectTimer?.invalidate() + self.reconnectTimer = nil self.handleEvent("connect", data: nil, isInternalMessage: false) } @@ -764,16 +770,15 @@ class SocketIOClient { // Something happened while polling func pollingDidFail(err:NSError?) { if !self.reconnecting { + self.connected = false self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } // We lost connection and should attempt to reestablish - func tryReconnect(var #triesLeft:Int) { - self.connected = false - - if triesLeft != -1 && triesLeft <= 0 { + @objc func tryReconnect() { + if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { self.didForceClose() return } else if self.connected { @@ -782,26 +787,25 @@ class SocketIOClient { return } - // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft - 1, isInternalMessage: true) - - let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) - - // Wait reconnectWait seconds and then check if connected. Repeat if not - dispatch_after(time, dispatch_get_main_queue()) {[weak self] in - if self == nil || self!.connected || self!.closed { + if self.reconnectTimer == nil { + self.reconnecting = true + dispatch_async(dispatch_get_main_queue()) {[weak self] in + if self == nil { + return + } + + self?.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self!.reconnectWait), + target: self!, selector: "tryReconnect", userInfo: nil, repeats: true) return } - if triesLeft != -1 { - triesLeft = triesLeft - 1 - } - - self!.tryReconnect(triesLeft: triesLeft) + return } - self.reconnecting = true + self.handleEvent("reconnectAttempt", data: self.reconnectAttempts - self.currentReconnectAttempt, + isInternalMessage: true) + + self.currentReconnectAttempt++ if self.paramConnect { self.connectWithParams(self.params) } else { @@ -817,7 +821,7 @@ class SocketIOClient { self.didForceClose() } else { self.handleEvent("reconnect", data: reason, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } @@ -830,7 +834,7 @@ class SocketIOClient { self.didForceClose() } else if !self.reconnecting { self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } } \ No newline at end of file From 41c168a29ac468d1c060f356dda5d03b11e606ec Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 16:29:13 -0500 Subject: [PATCH 44/64] Fixes. Add onAny method --- README.md | 3 +- SwiftIO/SocketEngine.swift | 69 +++++++++++++++++++++++------------- SwiftIO/SocketIOClient.swift | 24 +++++++++---- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index eaf9169..620b4f7 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Constructor Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. +2. `socket.onAny(callback:((event:String, items:AnyObject?)) -> Void)` - Adds a handler for all events. It will be called on any received event. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. 5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. 6. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. 7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index cedb457..a45e49e 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -51,6 +51,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( "emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private let parseQueue = dispatch_queue_create( + "parseQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var forcePolling = false @@ -162,7 +164,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - dispatch_async(self?.handleQueue) {[weak self] in + dispatch_async(self?.parseQueue) {[weak self] in self?.parsePollingMessage(str) return } @@ -189,11 +191,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func flushWaitingForPost() { - if self.postWait.count == 0 || !self.connected || !self.polling { + if self.postWait.count == 0 || !self.connected { + return + } else if self.websocket { + self.flushWaitingForPostToWebSocket() return } - let postStr = self.postWait.reduce("") {$0 + $1} + var postStr = "" + + for packet in self.postWait { + let len = countElements(packet) + + postStr += "\(len):\(packet)" + } + self.postWait.removeAll(keepCapacity: true) var req = NSMutableURLRequest(URL: @@ -220,9 +232,20 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false + self?.doPoll() } } + // We had packets waiting for send when we upgraded + // Send them raw + private func flushWaitingForPostToWebSocket() { + for msg in self.postWait { + self.ws?.send(msg) + } + + self.postWait.removeAll(keepCapacity: true) + } + // A poll failed, tell the client about it // We check to see if we were closed by the server first private func handlePollingFailed(reason:NSError?) { @@ -313,6 +336,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(str) + let strArray = Array(str) var length = "" var n = 0 var msg = "" @@ -328,7 +352,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } for var i = 0, l = str.length; i < l; i = i &+ 1 { - let strArray = Array(str) let chr = String(strArray[i]) if chr != ":" { @@ -349,8 +372,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if msg.length != 0 { - fixSwift = msg - self.parseEngineMessage(fixSwift) + // Be sure to capture the value of the msg + dispatch_async(self.handleQueue) {[weak self, msg] in + fixSwift = msg + self?.parseEngineMessage(fixSwift) + return + } } i += n @@ -360,7 +387,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func parseEngineMessage(message:AnyObject?) { - // println(message) + // println(message!) if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -382,6 +409,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO Handle other packets if messageString.hasPrefix("b4") { // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, end: advance(messageString.startIndex, 2))) @@ -391,6 +419,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.client.parseSocketMessage(data) } + return + } else if type == PacketType.NOOP.rawValue { + self.doPoll() return } @@ -456,32 +487,19 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func sendPollMessage(msg:String, withType type:PacketType, datas:[NSData]? = nil) { - // println("Sending: \(msg)") - var postData:NSData - var bDatas:[String]? + // println("Sending: poll: \(msg) as type: \(type.rawValue)") + let strMsg = "\(type.rawValue)\(msg)" + + self.postWait.append(strMsg) if datas != nil { - bDatas = [String]() for data in datas! { let (nilData, b64Data) = self.createBinaryDataForSend(data) - let dataLen = countElements(b64Data!) - bDatas!.append("\(dataLen):\(b64Data!)") + self.postWait.append(b64Data!) } } - let strMsg = "\(type.rawValue)\(msg)" - let postCount = countElements(strMsg) - var postStr = "\(postCount):\(strMsg)" - - if bDatas != nil { - for data in bDatas! { - postStr += data - } - } - - self.postWait.append(postStr) - if waitingForPost { self.doPoll() return @@ -491,6 +509,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { + // println("Sending: ws: \(str) as type: \(type.rawValue)") self.ws?.send("\(type.rawValue)\(str)") if datas != nil { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 516ee09..c3d76c8 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -36,6 +36,7 @@ class SocketIOClient { let reconnectAttempts:Int! private lazy var params = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() + private var anyHandler:((AnyHandler) -> Void)? private var currentAck = -1 private var currentReconnectAttempt = 0 private var forcePolling = false @@ -269,13 +270,17 @@ class SocketIOClient { func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { // println("Should do event: \(event) with data: \(data)") - dispatch_async(dispatch_get_main_queue()) { - if !self.connected && !isInternalMessage { - return - } - - for handler in self.handlers { - if handler.event == event { + if !self.connected && !isInternalMessage { + return + } + + dispatch_async(dispatch_get_main_queue()) {[weak self] in + self?.anyHandler?((event, data)) + return + } + for handler in self.handlers { + if handler.event == event { + dispatch_async(dispatch_get_main_queue()) { if data is NSArray { if ack != nil { handler.executeCallback(data as? NSArray, withAck: ack!, @@ -318,6 +323,11 @@ class SocketIOClient { self.handlers.append(handler) } + // Adds a handler for any event + func onAny(handler:(AnyHandler) -> Void) { + self.anyHandler = handler + } + // Opens the connection to the socket func open() { self.connect() From ae28a02163d7405891b284f329fd9ee95b587fc7 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 18:24:06 -0500 Subject: [PATCH 45/64] Use a ephemeral NSURLSession for polling, because NSURLConnection.sendAsnc.. seems to either leak memory, or cache --- SwiftIO/SocketEngine.swift | 32 +++++++++++++++----------------- SwiftIO/SocketEventHandler.swift | 1 + 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a45e49e..8d6b961 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -61,6 +61,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private var _polling = true private var probing = false private var probeWait = PollWaitQueue() + private let session:NSURLSession! private var waitingForPoll = false private var waitingForPost = false private var _websocket = false @@ -81,6 +82,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client self.forcePolling = forcePolling + self.session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration(), + delegate: nil, delegateQueue: self.workQueue) } func close() { @@ -144,12 +147,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let req = NSURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) self.waitingForPoll = true - NSURLConnection.sendAsynchronousRequest(req, - queue: self.workQueue) {[weak self] res, data, err in + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return } else if err != nil { @@ -161,7 +162,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(data) - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { // println(str) dispatch_async(self?.parseQueue) {[weak self] in @@ -172,7 +173,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.waitingForPoll = false self?.doPoll() - } + }.resume() } private func flushProbeWait() { @@ -206,10 +207,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { postStr += "\(len):\(packet)" } - self.postWait.removeAll(keepCapacity: true) + self.postWait.removeAll(keepCapacity: false) - var req = NSMutableURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -222,7 +222,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPBody = postData self.waitingForPost = true - NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in + + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if err != nil { if self!.polling { self?.handlePollingFailed(err) @@ -233,7 +234,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false self?.doPoll() - } + }.resume() } // We had packets waiting for send when we upgraded @@ -269,8 +270,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.urlWebSocket = urlWebSocket let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) - NSURLConnection.sendAsynchronousRequest(reqPolling, - queue: self.workQueue) {[weak self] res, data, err in + self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in var err:NSError? if self == nil { return @@ -325,7 +325,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.doPoll() self?.startPingTimer() } - } + }.resume() } // Translatation of engine.io-parser#decodePayload @@ -477,8 +477,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPing() { - // println("sending ping") - if self.websocket { self.sendWebSocketMessage("", withType: PacketType.PING) } else { @@ -527,7 +525,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.pingInterval == nil { return } - + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 3fe9204..87e8a16 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,6 +23,7 @@ // THE SOFTWARE. typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +typealias AnyHandler = (event:String, items:AnyObject?) typealias AckEmitter = (AnyObject...) -> Void private func emitAckCallback(socket:SocketIOClient, num:Int, type:Int) -> AckEmitter { From 14bf64233b7bb3b841c7c8abcfa0236cbe374a00 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 18:38:12 -0500 Subject: [PATCH 46/64] add example for onAny --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 620b4f7..5c0d49b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) +// Called on every event +socket.onAny {println("got event: \($0.event) with items \($0.items)")} + // Socket Events socket.on("connect") {data, ack in println("socket connected") From 6399df4c337383c9bbc17ecd6ad2e9feac9751ba Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 21:17:26 -0500 Subject: [PATCH 47/64] tweaks --- SwiftIO/SocketEngine.swift | 154 ++++++++++++++++--------------- SwiftIO/SocketEventHandler.swift | 5 +- SwiftIO/SocketIOClient.swift | 44 +++++---- 3 files changed, 103 insertions(+), 100 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 8d6b961..1c74338 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -151,29 +151,29 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.waitingForPoll = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in - if self == nil { - return - } else if err != nil { - if self!.polling { - self?.handlePollingFailed(err) - } + if self == nil { + return + } else if err != nil { + if self!.polling { + self?.handlePollingFailed(err) + } + return + } + + // println(data) + + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { + // println(str) + + dispatch_async(self?.parseQueue) {[weak self] in + self?.parsePollingMessage(str) return } - - // println(data) - - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { - // println(str) - - dispatch_async(self?.parseQueue) {[weak self] in - self?.parsePollingMessage(str) - return - } - } - - self?.waitingForPoll = false - self?.doPoll() - }.resume() + } + + self?.waitingForPoll = false + self?.doPoll() + }.resume() } private func flushProbeWait() { @@ -229,12 +229,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.handlePollingFailed(err) } return + } else if self == nil { + return } - self?.flushWaitingForPost() self?.waitingForPost = false - self?.doPoll() - }.resume() + dispatch_async(self!.emitQueue) { + self?.flushWaitingForPost() + self?.doPoll() + return + }}.resume() } // We had packets waiting for send when we upgraded @@ -252,6 +256,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed(reason:NSError?) { if !self.client.reconnecting { self.connected = false + self.ws?.close() self.pingTimer?.invalidate() self.waitingForPoll = false self.waitingForPost = false @@ -271,61 +276,58 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in - var err:NSError? - if self == nil { - return - } else if err != nil || data == nil { - if self!.polling { - self?.handlePollingFailed(err) - } + var err2:NSError? + if self == nil { + return + } else if err != nil || data == nil { + self?.handlePollingFailed(err) + return + } + + if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { + var mutString = RegexMutable(dataString) + let parsed:[String]? = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() + + if parsed == nil || parsed?.count != 4 { return } - if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { - var mutString = RegexMutable(dataString) - let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() - - if parsed.count != 4 { - return - } - - let length = parsed[1] - let type = parsed[2] - let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - - if type != "0" { - NSLog("Error handshaking") - return - } - - self?.connected = true - - if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - // println(json) - self?.sid = sid - - if !self!.forcePolling { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() - } - } else { - NSLog("Error handshaking") - return - } - - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 - } - } - - self?.doPoll() - self?.startPingTimer() + let length = parsed![1] + let type = parsed![2] + let jsonData = parsed![3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + if type != "0" { + NSLog("Error handshaking") + return } - }.resume() + + self?.connected = true + + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err2) as? NSDictionary { + if let sid = json["sid"] as? String { + // println(json) + self?.sid = sid + + if !self!.forcePolling { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + } + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } + } + + self?.doPoll() + self?.startPingTimer() + }}.resume() } // Translatation of engine.io-parser#decodePayload @@ -525,7 +527,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.pingInterval == nil { return } - + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 87e8a16..c59e92c 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -45,6 +45,9 @@ class SocketEventHandler { func executeCallback(_ items:NSArray? = nil, withAck ack:Int? = nil, withAckType type:Int? = nil, withSocket socket:SocketIOClient? = nil) { - callback?(items, ack != nil ? emitAckCallback(socket!, ack!, type!) : nil) + dispatch_async(dispatch_get_main_queue()) {[weak self] in + self?.callback?(items, ack != nil ? emitAckCallback(socket!, ack!, type!) : nil) + return + } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index c3d76c8..a37f641 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -280,30 +280,28 @@ class SocketIOClient { } for handler in self.handlers { if handler.event == event { - dispatch_async(dispatch_get_main_queue()) { - if data is NSArray { - if ack != nil { - handler.executeCallback(data as? NSArray, withAck: ack!, - withAckType: ackType, withSocket: self) - } else { - handler.executeCallback(data as? NSArray) - } + if data is NSArray { + if ack != nil { + handler.executeCallback(data as? NSArray, withAck: ack!, + withAckType: ackType, withSocket: self) } else { - - // Trying to do a ternary expression in the executeCallback method - // seemed to crash Swift - var dataArr:NSArray? = nil - - if let data:AnyObject = data { - dataArr = [data] - } - - if ack != nil { - handler.executeCallback(dataArr, withAck: ack!, - withAckType: ackType, withSocket: self) - } else { - handler.executeCallback(dataArr) - } + handler.executeCallback(data as? NSArray) + } + } else { + + // Trying to do a ternary expression in the executeCallback method + // seemed to crash Swift + var dataArr:NSArray? = nil + + if let data:AnyObject = data { + dataArr = [data] + } + + if ack != nil { + handler.executeCallback(dataArr, withAck: ack!, + withAckType: ackType, withSocket: self) + } else { + handler.executeCallback(dataArr) } } } From 3118b0851de3ad86779cf03316f670180512de8e Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 7 Mar 2015 19:49:44 -0500 Subject: [PATCH 48/64] add link to example project --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5c0d49b..be436f4 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,11 @@ socket.emit("testData", [ allowLossyConversion: false)!, "test": true]) ``` + +Detailed Example +================ +A more detailed example can be found [here](https://github.com/nuclearace/socket.io-client-swift-example) + License ======= MIT From 8365ded2155a6313c7205c02d46118ea00cc0350 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 13:33:54 -0400 Subject: [PATCH 49/64] Use starscream for websockets. Execute emitAcks on the main queue. --- README.md | 4 +- SwiftIO/SocketAckHandler.swift | 7 + SwiftIO/SocketEngine.swift | 105 ++--- SwiftIO/SocketEventHandler.swift | 2 + SwiftIO/SocketIOClient.swift | 239 +++++------ SwiftIO/WebSocket.swift | 714 +++++++++++++++++++++++++++++++ 6 files changed, 880 insertions(+), 191 deletions(-) create mode 100644 SwiftIO/WebSocket.swift diff --git a/README.md b/README.md index be436f4..3ac0c44 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,7 @@ For Swift 1.2 use the 1.2 branch. Installation ============ -1. Requires linking [SocketRocket](https://github.com/square/SocketRocket) against your xcode project. (Be sure to link the [frameworks](https://github.com/square/SocketRocket#framework-dependencies) required by SocketRocket) -2. Create a bridging header for SocketRocket -3. Copy the SwiftIO folder into your xcode project +1. Copy the SwiftIO folder into your Xcode project! API === diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index 17a4236..db4fe3f 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -39,4 +39,11 @@ class SocketAckHandler { func onAck(callback:AckCallback) { self.callback = callback } + + func executeAck(data:NSArray?) { + dispatch_async(dispatch_get_main_queue()) {[cb = self.callback] in + cb?(data) + return + } + } } \ No newline at end of file diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 1c74338..817097a 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -46,7 +46,7 @@ private enum PacketType: String { case NOOP = "6" } -class SocketEngine: NSObject, SRWebSocketDelegate { +class SocketEngine: NSObject, WebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( @@ -77,7 +77,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { var websocket:Bool { return self._websocket } - var ws:SRWebSocket? + var ws:WebSocket? init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client @@ -165,7 +165,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { // println(str) - dispatch_async(self?.parseQueue) {[weak self] in + dispatch_async(self!.parseQueue) {[weak self] in + if self == nil { + return + } + self?.parsePollingMessage(str) return } @@ -224,12 +228,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.waitingForPost = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in - if err != nil { - if self!.polling { - self?.handlePollingFailed(err) - } + if self == nil { return - } else if self == nil { + } else if err != nil && self!.polling { + self?.handlePollingFailed(err) return } @@ -245,7 +247,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Send them raw private func flushWaitingForPostToWebSocket() { for msg in self.postWait { - self.ws?.send(msg) + self.ws?.writeString(msg) } self.postWait.removeAll(keepCapacity: true) @@ -256,7 +258,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed(reason:NSError?) { if !self.client.reconnecting { self.connected = false - self.ws?.close() + self.ws?.disconnect() self.pingTimer?.invalidate() self.waitingForPoll = false self.waitingForPost = false @@ -310,10 +312,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.sid = sid if !self!.forcePolling { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws = WebSocket(url: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.queue = self?.handleQueue self?.ws?.delegate = self - self?.ws?.open() + self?.ws?.connect() } } else { NSLog("Error handshaking") @@ -377,8 +379,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in fixSwift = msg - self?.parseEngineMessage(fixSwift) - return + if fixSwift is String { + self?.parseEngineMessage(fixSwift as String) + } } } @@ -388,16 +391,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - private func parseEngineMessage(message:AnyObject?) { + private func parseEngineData(data:NSData) { + self.client.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + } + + private func parseEngineMessage(var message:String) { // println(message!) - if let data = message as? NSData { - // Strip off message type - self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - return - } - var messageString = message as String - var strMessage = RegexMutable(messageString) + var strMessage = RegexMutable(message) // We should upgrade if strMessage == "3probe" { @@ -409,16 +410,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - if messageString.hasPrefix("b4") { + if message.hasPrefix("b4") { // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) + message.removeRange(Range(start: message.startIndex, + end: advance(message.startIndex, 2))) - if let data = NSData(base64EncodedString: messageString, + if let data = NSData(base64EncodedString: message, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { // println("sending \(data)") - self.client.parseSocketMessage(data) + self.client.parseBinaryData(data) } return @@ -427,7 +428,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - if messageString == PacketType.CLOSE.rawValue { + if message == PacketType.CLOSE.rawValue { // do nothing return } @@ -436,10 +437,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } // Remove message type - messageString.removeAtIndex(messageString.startIndex) + message.removeAtIndex(message.startIndex) // println("sending \(messageString)") - self.client.parseSocketMessage(messageString) + self.client.parseSocketMessage(message) } private func probeWebSocket() { @@ -480,6 +481,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func sendPing() { if self.websocket { + self.ws?.writePing(NSData()) self.sendWebSocketMessage("", withType: PacketType.PING) } else { self.sendPollMessage("", withType: PacketType.PING) @@ -510,13 +512,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending: ws: \(str) as type: \(type.rawValue)") - self.ws?.send("\(type.rawValue)\(str)") + self.ws?.writeString("\(type.rawValue)\(str)") if datas != nil { for data in datas! { let (data, nilString) = self.createBinaryDataForSend(data) if data != nil { - self.ws?.send(data!) + self.ws?.writeData(data!) } } } @@ -546,25 +548,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // Called when a message is recieved - func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - // println(message) - - dispatch_async(self.handleQueue) {[weak self] in - self?.parseEngineMessage(message) - return - } - } - - // Called when the socket is opened - func webSocketDidOpen(webSocket:SRWebSocket!) { + func websocketDidConnect(socket:WebSocket) { self.websocketConnected = true self.probing = true self.probeWebSocket() } - // Called when the socket is closed - func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + func websocketDidDisconnect(socket:WebSocket, error:NSError?) { self.websocketConnected = false self.probing = false @@ -573,24 +563,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.connected = false self._websocket = false self._polling = true - self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + self.client.webSocketDidCloseWithCode(1, reason: "Socket Disconnect", wasClean: true) } else { self.flushProbeWait() } } - // Called when an error occurs. - func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.websocketConnected = false - self._polling = true - self.probing = false - - if self.websocket { - self.pingTimer?.invalidate() - self.connected = false - self.client.webSocketDidFailWithError(error) - } else { - self.flushProbeWait() - } + func websocketDidReceiveMessage(socket:WebSocket, text:String) { + self.parseEngineMessage(text) + } + + func websocketDidReceiveData(socket:WebSocket, data:NSData) { + self.parseEngineData(data) } } \ No newline at end of file diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index c59e92c..2294025 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -22,6 +22,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +import Foundation + typealias NormalCallback = (NSArray?, AckEmitter?) -> Void typealias AnyHandler = (event:String, items:AnyObject?) typealias AckEmitter = (AnyObject...) -> Void diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index a37f641..020b977 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -25,7 +25,6 @@ import Foundation class SocketIOClient { - let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -48,6 +47,7 @@ class SocketIOClient { var closed = false var connected = false var connecting = false + var engine:SocketEngine? var nsp:String? var reconnects = true var reconnecting = false @@ -68,7 +68,6 @@ class SocketIOClient { mutURL = mutURL["https://"] ~= "" self.socketURL = mutURL - self.reconnectAttempts = -1 // Set options if opts != nil { @@ -78,6 +77,8 @@ class SocketIOClient { if let reconnectAttempts = opts!["reconnectAttempts"] as? Int { self.reconnectAttempts = reconnectAttempts + } else { + self.reconnectAttempts = -1 } if let reconnectWait = opts!["reconnectWait"] as? Int { @@ -91,6 +92,8 @@ class SocketIOClient { if let polling = opts!["forcePolling"] as? Bool { self.forcePolling = polling } + } else { + self.reconnectAttempts = -1 } self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) @@ -112,7 +115,7 @@ class SocketIOClient { self.closed = false } - self.engine.open() + self.engine?.open() } // Connect to the server using params @@ -125,7 +128,7 @@ class SocketIOClient { self.params = params self.paramConnect = true - self.engine.open(opts: params) + self.engine?.open(opts: params) } func didConnect() { @@ -254,11 +257,11 @@ class SocketIOClient { return true } else { if data is NSArray { - handler.callback?(data as? NSArray) + handler.executeAck(data as? NSArray) } else if data != nil { - handler.callback?([data!]) + handler.executeAck([data!]) } else { - handler.callback?(nil) + handler.executeAck(nil) } return false @@ -494,77 +497,86 @@ class SocketIOClient { } // Parses messages recieved - func parseSocketMessage(message:AnyObject?) { - if message == nil { - return - } - + func parseSocketMessage(stringMessage:String) { // println(message!) - if let stringMessage = message as? String { - // Check for successful namepsace connect + // Check for successful namepsace connect + if self.nsp != nil { + if stringMessage == "0/\(self.nsp!)" { + self.didConnect() + return + } + } + + if stringMessage == "0" { if self.nsp != nil { - if stringMessage == "0/\(self.nsp!)" { - self.didConnect() - return - } + // Join namespace + self.joinNamespace() + return + } else { + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.didConnect() + return + } + } + + var mutMessage = RegexMutable(stringMessage) + + /** + Begin check for message + **/ + let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() + + if messageGroups[1].hasPrefix("2") { + var mesNum = messageGroups[1] + var ackNum:String + var namespace:String? + var messagePart:String! + + if messageGroups[3] != "" { + ackNum = messageGroups[3] + } else { + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) + mesNum.replaceRange(range, with: "") + ackNum = mesNum } - if stringMessage == "0" { - if self.nsp != nil { - // Join namespace - self.joinNamespace() - return - } else { - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.didConnect() - return - } + namespace = messageGroups[2] + messagePart = messageGroups[4] + + if namespace == "" && self.nsp != nil { + return } - var mutMessage = RegexMutable(stringMessage) - - /** - Begin check for message - **/ - let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - - if messageGroups[1].hasPrefix("2") { - var mesNum = messageGroups[1] - var ackNum:String - var namespace:String? - var messagePart:String! + let messageInternals = RegexMutable(messagePart)["\\[\"(.*?)\",(.*?)?\\]$"].groups() + if messageInternals != nil && messageInternals.count > 2 { + let event = messageInternals[1] + var data:String? - if messageGroups[3] != "" { - ackNum = messageGroups[3] + if messageInternals[2] == "" { + data = nil } else { - let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) - mesNum.replaceRange(range, with: "") - ackNum = mesNum + data = messageInternals[2] } - namespace = messageGroups[2] - messagePart = messageGroups[4] - - if namespace == "" && self.nsp != nil { - return - } - - let messageInternals = RegexMutable(messagePart)["\\[\"(.*?)\",(.*?)?\\]$"].groups() - if messageInternals != nil && messageInternals.count > 2 { - let event = messageInternals[1] - var data:String? - - if messageInternals[2] == "" { - data = nil + // It would be nice if socket.io only allowed one thing + // per message, but alas, it doesn't. + if let parsed:AnyObject = SocketIOClient.parseData(data) { + if ackNum == "" { + self.handleEvent(event, data: parsed) } else { - data = messageInternals[2] + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) } - - // It would be nice if socket.io only allowed one thing - // per message, but alas, it doesn't. - if let parsed:AnyObject = SocketIOClient.parseData(data) { + return + } else if let strData = data { + // There are multiple items in the message + // Turn it into a String and run it through + // parseData to try and get an array. + let asArray = "[\(strData)]" + if let parsed:AnyObject = SocketIOClient.parseData(asArray) { if ackNum == "" { self.handleEvent(event, data: parsed) } else { @@ -573,78 +585,53 @@ class SocketIOClient { wantsAck: ackNum.toInt(), withAckType: 3) } return - } else if let strData = data { - // There are multiple items in the message - // Turn it into a String and run it through - // parseData to try and get an array. - let asArray = "[\(strData)]" - if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - if ackNum == "" { - self.handleEvent(event, data: parsed) - } else { - self.currentAck = ackNum.toInt()! - self.handleEvent(event, data: parsed, isInternalMessage: false, - wantsAck: ackNum.toInt(), withAckType: 3) - } - return - } } } - - // Check for no item event - let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() - if noItemMessage != nil && noItemMessage.count == 2 { - let event = noItemMessage[1] - if ackNum == "" { - self.handleEvent(event, data: nil) - } else { - self.currentAck = ackNum.toInt()! - self.handleEvent(event, data: nil, isInternalMessage: false, - wantsAck: ackNum.toInt(), withAckType: 3) - } - return - } - } else if messageGroups[1].hasPrefix("3") { - let arr = Array(messageGroups[1]) - var ackNum:String - let nsp = messageGroups[2] - - if nsp == "" && self.nsp != nil { - return - } - - if nsp == "" { - ackNum = String(arr[1...arr.count-1]) + } + + // Check for no item event + let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() + if noItemMessage != nil && noItemMessage.count == 2 { + let event = noItemMessage[1] + if ackNum == "" { + self.handleEvent(event, data: nil) } else { - ackNum = messageGroups[3] + self.currentAck = ackNum.toInt()! + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) } - - let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) - self.handleAck(ackNum.toInt()!, data: ackData) - return } - /** - End Check for message - **/ + } else if messageGroups[1].hasPrefix("3") { + let arr = Array(messageGroups[1]) + var ackNum:String + let nsp = messageGroups[2] - // Check for message with binary placeholders - self.parseBinaryMessage(message: message!) + if nsp == "" && self.nsp != nil { + return + } + + if nsp == "" { + ackNum = String(arr[1...arr.count-1]) + } else { + ackNum = messageGroups[3] + } + + let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) + self.handleAck(ackNum.toInt()!, data: ackData) + + return } + /** + End Check for message + **/ - // Message is binary - if let binary = message as? NSData { - if self.waitingData.isEmpty { - return - } - - self.parseBinaryData(binary) - } + // Check for message with binary placeholders + self.parseBinaryMessage(message: stringMessage) } // Tries to parse a message that contains binary private func parseBinaryMessage(#message:AnyObject) { - // println(message) if let stringMessage = message as? String { var mutMessage = RegexMutable(stringMessage) @@ -731,7 +718,7 @@ class SocketIOClient { } // Handles binary data - private func parseBinaryData(data:NSData) { + func parseBinaryData(data:NSData) { let shouldExecute = self.waitingData[0].addData(data) if shouldExecute { @@ -806,8 +793,6 @@ class SocketIOClient { target: self!, selector: "tryReconnect", userInfo: nil, repeats: true) return } - - return } self.handleEvent("reconnectAttempt", data: self.reconnectAttempts - self.currentReconnectAttempt, diff --git a/SwiftIO/WebSocket.swift b/SwiftIO/WebSocket.swift new file mode 100644 index 0000000..c74e2b3 --- /dev/null +++ b/SwiftIO/WebSocket.swift @@ -0,0 +1,714 @@ +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Websocket.swift +// +// Created by Dalton Cherry on 7/16/14. +// +////////////////////////////////////////////////////////////////////////////////////////////////// + +import Foundation +import CoreFoundation + +public protocol WebSocketDelegate: class { + func websocketDidConnect(socket: WebSocket) + func websocketDidDisconnect(socket: WebSocket, error: NSError?) + func websocketDidReceiveMessage(socket: WebSocket, text: String) + func websocketDidReceiveData(socket: WebSocket, data: NSData) +} + +public class WebSocket : NSObject, NSStreamDelegate { + + enum OpCode : UInt8 { + case ContinueFrame = 0x0 + case TextFrame = 0x1 + case BinaryFrame = 0x2 + //3-7 are reserved. + case ConnectionClose = 0x8 + case Ping = 0x9 + case Pong = 0xA + //B-F reserved. + } + + enum CloseCode : UInt16 { + case Normal = 1000 + case GoingAway = 1001 + case ProtocolError = 1002 + case ProtocolUnhandledType = 1003 + // 1004 reserved. + case NoStatusReceived = 1005 + //1006 reserved. + case Encoding = 1007 + case PolicyViolated = 1008 + case MessageTooBig = 1009 + } + + enum InternalErrorCode : UInt16 { + // 0-999 WebSocket status codes not used + case OutputStreamWriteError = 1 + } + + //Where the callback is executed. It defaults to the main UI thread queue. + public var queue = dispatch_get_main_queue() + + var optionalProtocols : Array? + //Constant Values. + let headerWSUpgradeName = "Upgrade" + let headerWSUpgradeValue = "websocket" + let headerWSHostName = "Host" + let headerWSConnectionName = "Connection" + let headerWSConnectionValue = "Upgrade" + let headerWSProtocolName = "Sec-WebSocket-Protocol" + let headerWSVersionName = "Sec-WebSocket-Version" + let headerWSVersionValue = "13" + let headerWSKeyName = "Sec-WebSocket-Key" + let headerOriginName = "Origin" + let headerWSAcceptName = "Sec-WebSocket-Accept" + let BUFFER_MAX = 2048 + let FinMask: UInt8 = 0x80 + let OpCodeMask: UInt8 = 0x0F + let RSVMask: UInt8 = 0x70 + let MaskMask: UInt8 = 0x80 + let PayloadLenMask: UInt8 = 0x7F + let MaxFrameSize: Int = 32 + + class WSResponse { + var isFin = false + var code: OpCode = .ContinueFrame + var bytesLeft = 0 + var frameCount = 0 + var buffer: NSMutableData? + } + + public weak var delegate: WebSocketDelegate? + private var url: NSURL + private var inputStream: NSInputStream? + private var outputStream: NSOutputStream? + private var isRunLoop = false + private var connected = false + private var writeQueue: NSOperationQueue? + private var readStack = Array() + private var inputQueue = Array() + private var fragBuffer: NSData? + public var headers = Dictionary() + public var voipEnabled = false + public var selfSignedSSL = false + private var connectedBlock: ((Void) -> Void)? = nil + private var disconnectedBlock: ((NSError?) -> Void)? = nil + private var receivedTextBlock: ((String) -> Void)? = nil + private var receivedDataBlock: ((NSData) -> Void)? = nil + public var isConnected :Bool { + return connected + } + + //init the websocket with a url + public init(url: NSURL) { + self.url = url + } + //used for setting protocols. + public convenience init(url: NSURL, protocols: Array) { + self.init(url: url) + optionalProtocols = protocols + } + //closure based instead of the delegate + public convenience init(url: NSURL, protocols: Array, connect:((Void) -> Void), disconnect:((NSError?) -> Void), text:((String) -> Void), data:(NSData) -> Void) { + self.init(url: url, protocols: protocols) + connectedBlock = connect + disconnectedBlock = disconnect + receivedTextBlock = text + receivedDataBlock = data + } + //same as above, just shorter + public convenience init(url: NSURL, connect:((Void) -> Void), disconnect:((NSError?) -> Void), text:((String) -> Void)) { + self.init(url: url) + connectedBlock = connect + disconnectedBlock = disconnect + receivedTextBlock = text + } + //same as above, just shorter + public convenience init(url: NSURL, connect:((Void) -> Void), disconnect:((NSError?) -> Void), data:((NSData) -> Void)) { + self.init(url: url) + connectedBlock = connect + disconnectedBlock = disconnect + receivedDataBlock = data + } + + ///Connect to the websocket server on a background thread + public func connect() { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { + self.createHTTPRequest() + }) + } + + ///disconnect from the websocket server + public func disconnect() { + writeError(CloseCode.Normal.rawValue) + } + + ///write a string to the websocket. This sends it as a text frame. + public func writeString(str: String) { + dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame) + } + + ///write binary data to the websocket. This sends it as a binary frame. + public func writeData(data: NSData) { + dequeueWrite(data, code: .BinaryFrame) + } + + //write a ping to the websocket. This sends it as a control frame. + //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s + public func writePing(data: NSData) { + dequeueWrite(data, code: .Ping) + } + //private methods below! + + //private method that starts the connection + private func createHTTPRequest() { + + let str: NSString = url.absoluteString! + let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", + url, kCFHTTPVersion1_1) + + var port = url.port + if port == nil { + if url.scheme == "wss" || url.scheme == "https" { + port = 443 + } else { + port = 80 + } + } + self.addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) + self.addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) + if let protocols = optionalProtocols { + self.addHeader(urlRequest, key: headerWSProtocolName, val: ",".join(protocols)) + } + self.addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) + self.addHeader(urlRequest, key: headerWSKeyName, val: self.generateWebSocketKey()) + self.addHeader(urlRequest, key: headerOriginName, val: url.absoluteString!) + self.addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") + for (key,value) in headers { + self.addHeader(urlRequest, key: key, val: value) + } + + let serializedRequest: NSData = CFHTTPMessageCopySerializedMessage(urlRequest.takeUnretainedValue()).takeUnretainedValue() + self.initStreamsWithData(serializedRequest, Int(port!)) + } + //Add a header to the CFHTTPMessage by using the NSString bridges to CFString + private func addHeader(urlRequest: Unmanaged,key: String, val: String) { + let nsKey: NSString = key + let nsVal: NSString = val + CFHTTPMessageSetHeaderFieldValue(urlRequest.takeUnretainedValue(), + nsKey, + nsVal) + } + //generate a websocket key as needed in rfc + private func generateWebSocketKey() -> String { + var key = "" + let seed = 16 + for (var i = 0; i < seed; i++) { + let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25))) + key += "\(Character(uni))" + } + var data = key.dataUsingEncoding(NSUTF8StringEncoding) + var baseKey = data?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(0)) + return baseKey! + } + //Start the stream connection and write the data to the output stream + private func initStreamsWithData(data: NSData, _ port: Int) { + //higher level API we will cut over to at some point + //NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream) + + var readStream: Unmanaged? + var writeStream: Unmanaged? + let h: NSString = url.host! + CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) + inputStream = readStream!.takeUnretainedValue() + outputStream = writeStream!.takeUnretainedValue() + + inputStream!.delegate = self + outputStream!.delegate = self + if url.scheme == "wss" || url.scheme == "https" { + inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) + } + if self.voipEnabled { + inputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + outputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) + } + if self.selfSignedSSL { + let settings: Dictionary = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] + inputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings) + outputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings) + } + isRunLoop = true + inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream!.open() + outputStream!.open() + let bytes = UnsafePointer(data.bytes) + outputStream!.write(bytes, maxLength: data.length) + while(isRunLoop) { + NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as NSDate) + } + } + //delegate for the stream methods. Processes incoming bytes + func stream(aStream: NSStream!, handleEvent eventCode: NSStreamEvent) { + + if eventCode == .HasBytesAvailable { + if(aStream == inputStream) { + processInputStream() + } + } else if eventCode == .ErrorOccurred { + disconnectStream(aStream!.streamError) + } else if eventCode == .EndEncountered { + disconnectStream(nil) + } + } + //disconnect the stream object + private func disconnectStream(error: NSError?) { + if writeQueue != nil { + writeQueue!.waitUntilAllOperationsAreFinished() + } + inputStream!.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + outputStream!.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + inputStream!.close() + outputStream!.close() + inputStream = nil + outputStream = nil + isRunLoop = false + connected = false + dispatch_async(queue,{ + if let disconnectBlock = self.disconnectedBlock { + disconnectBlock(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + }) + } + + ///handles the incoming bytes and sending them to the proper processing method + private func processInputStream() { + let buf = NSMutableData(capacity: BUFFER_MAX) + var buffer = UnsafeMutablePointer(buf!.bytes) + let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) + if length > 0 { + if !connected { + connected = processHTTP(buffer, bufferLen: length) + if !connected { + dispatch_async(queue,{ + //self.workaroundMethod() + let error = self.errorWithDetail("Invalid HTTP upgrade", code: 1) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + }) + } + } else { + var process = false + if inputQueue.count == 0 { + process = true + } + inputQueue.append(NSData(bytes: buffer, length: length)) + if process { + dequeueInput() + } + } + } + } + ///dequeue the incoming input so it is processed in order + private func dequeueInput() { + if inputQueue.count > 0 { + let data = inputQueue[0] + var work = data + if (fragBuffer != nil) { + var combine = NSMutableData(data: fragBuffer!) + combine.appendData(data) + work = combine + fragBuffer = nil + } + let buffer = UnsafePointer(work.bytes) + processRawMessage(buffer, bufferLen: work.length) + inputQueue = inputQueue.filter{$0 != data} + dequeueInput() + } + } + ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. + private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Bool { + let CRLFBytes = [UInt8("\r"), UInt8("\n"), UInt8("\r"), UInt8("\n")] + var k = 0 + var totalSize = 0 + for var i = 0; i < bufferLen; i++ { + if buffer[i] == CRLFBytes[k] { + k++ + if k == 3 { + totalSize = i + 1 + break + } + } else { + k = 0 + } + } + if totalSize > 0 { + if validateResponse(buffer, bufferLen: totalSize) { + dispatch_async(queue,{ + //self.workaroundMethod() + if let connectBlock = self.connectedBlock { + connectBlock() + } + self.delegate?.websocketDidConnect(self) + }) + totalSize += 1 //skip the last \n + let restSize = bufferLen - totalSize + if restSize > 0 { + processRawMessage((buffer+totalSize),bufferLen: restSize) + } + return true + } + } + return false + } + + ///validates the HTTP is a 101 as per the RFC spec + private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Bool { + let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, 0) + CFHTTPMessageAppendBytes(response.takeUnretainedValue(), buffer, bufferLen) + if CFHTTPMessageGetResponseStatusCode(response.takeUnretainedValue()) != 101 { + return false + } + let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response.takeUnretainedValue()) + let headers: NSDictionary = cfHeaders.takeUnretainedValue() + let acceptKey = headers[headerWSAcceptName] as NSString + if acceptKey.length > 0 { + return true + } + return false + } + + ///process the websocket data + private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { + var response = readStack.last + if response != nil && bufferLen < 2 { + fragBuffer = NSData(bytes: buffer, length: bufferLen) + return + } + if response != nil && response!.bytesLeft > 0 { + let resp = response! + var len = resp.bytesLeft + var extra = bufferLen - resp.bytesLeft + if resp.bytesLeft > bufferLen { + len = bufferLen + extra = 0 + } + resp.bytesLeft -= len + resp.buffer?.appendData(NSData(bytes: buffer, length: len)) + processResponse(resp) + var offset = bufferLen - extra + if extra > 0 { + processExtra((buffer+offset), bufferLen: extra) + } + return + } else { + let isFin = (FinMask & buffer[0]) + let receivedOpcode = (OpCodeMask & buffer[0]) + let isMasked = (MaskMask & buffer[1]) + let payloadLen = (PayloadLenMask & buffer[1]) + var offset = 2 + if((isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != OpCode.Pong.rawValue) { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("masked and rsv data is not currently supported", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + let isControlFrame = (receivedOpcode == OpCode.ConnectionClose.rawValue || receivedOpcode == OpCode.Ping.rawValue) + if !isControlFrame && (receivedOpcode != OpCode.BinaryFrame.rawValue && receivedOpcode != OpCode.ContinueFrame.rawValue && + receivedOpcode != OpCode.TextFrame.rawValue && receivedOpcode != OpCode.Pong.rawValue) { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + if isControlFrame && isFin == 0 { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("control frames can't be fragmented", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + if receivedOpcode == OpCode.ConnectionClose.rawValue { + var code = CloseCode.Normal.rawValue + if payloadLen == 1 { + code = CloseCode.ProtocolError.rawValue + } else if payloadLen > 1 { + var codeBuffer = UnsafePointer((buffer+offset)) + code = codeBuffer[0].byteSwapped + if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { + code = CloseCode.ProtocolError.rawValue + } + offset += 2 + } + if payloadLen > 2 { + let len = Int(payloadLen-2) + if len > 0 { + let bytes = UnsafePointer((buffer+offset)) + var str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) + if str == nil { + code = CloseCode.ProtocolError.rawValue + } + } + } + let error = self.errorWithDetail("connection closed by server", code: code) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(code) + return + } + if isControlFrame && payloadLen > 125 { + writeError(CloseCode.ProtocolError.rawValue) + return + } + var dataLength = UInt64(payloadLen) + if dataLength == 127 { + let bytes = UnsafePointer((buffer+offset)) + dataLength = bytes[0].byteSwapped + offset += sizeof(UInt64) + } else if dataLength == 126 { + let bytes = UnsafePointer((buffer+offset)) + dataLength = UInt64(bytes[0].byteSwapped) + offset += sizeof(UInt16) + } + var len = dataLength + if dataLength > UInt64(bufferLen) { + len = UInt64(bufferLen-offset) + } + var data: NSData! + if len < 0 { + len = 0 + data = NSData() + } else { + data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) + } + if receivedOpcode == OpCode.Pong.rawValue { + let step = Int(offset+len) + let extra = bufferLen-step + if extra > 0 { + processRawMessage((buffer+step), bufferLen: extra) + } + return + } + var response = readStack.last + if isControlFrame { + response = nil //don't append pings + } + if isFin == 0 && receivedOpcode == OpCode.ContinueFrame.rawValue && response == nil { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("continue frame before a binary or text frame", code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + var isNew = false + if(response == nil) { + if receivedOpcode == OpCode.ContinueFrame.rawValue { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("first frame can't be a continue frame", + code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + isNew = true + response = WSResponse() + response!.code = OpCode(rawValue: receivedOpcode)! + response!.bytesLeft = Int(dataLength) + response!.buffer = NSMutableData(data: data) + } else { + if receivedOpcode == OpCode.ContinueFrame.rawValue { + response!.bytesLeft = Int(dataLength) + } else { + let errCode = CloseCode.ProtocolError.rawValue + let error = self.errorWithDetail("second and beyond of fragment message must be a continue frame", + code: errCode) + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + writeError(errCode) + return + } + response!.buffer!.appendData(data) + } + if response != nil { + response!.bytesLeft -= Int(len) + response!.frameCount++ + response!.isFin = isFin > 0 ? true : false + if(isNew) { + readStack.append(response!) + } + processResponse(response!) + } + + let step = Int(offset+len) + let extra = bufferLen-step + if(extra > 0) { + processExtra((buffer+step), bufferLen: extra) + } + } + + } + + ///process the extra of a buffer + private func processExtra(buffer: UnsafePointer, bufferLen: Int) { + if bufferLen < 2 { + fragBuffer = NSData(bytes: buffer, length: bufferLen) + } else { + processRawMessage(buffer, bufferLen: bufferLen) + } + } + + ///process the finished response of a buffer + private func processResponse(response: WSResponse) -> Bool { + if response.isFin && response.bytesLeft <= 0 { + if response.code == .Ping { + let data = response.buffer! //local copy so it is perverse for writing + dequeueWrite(data, code: OpCode.Pong) + } else if response.code == .TextFrame { + var str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) + if str == nil { + writeError(CloseCode.Encoding.rawValue) + return false + } + dispatch_async(queue,{ + if let textBlock = self.receivedTextBlock{ + textBlock(str!) + } + self.delegate?.websocketDidReceiveMessage(self, text: str!) + }) + } else if response.code == .BinaryFrame { + let data = response.buffer! //local copy so it is perverse for writing + dispatch_async(queue,{ + //self.workaroundMethod() + if let dataBlock = self.receivedDataBlock{ + dataBlock(data) + } + self.delegate?.websocketDidReceiveData(self, data: data) + }) + } + readStack.removeLast() + return true + } + return false + } + + ///Create an error + private func errorWithDetail(detail: String, code: UInt16) -> NSError { + var details = Dictionary() + details[NSLocalizedDescriptionKey] = detail + return NSError(domain: "Websocket", code: Int(code), userInfo: details) + } + + ///write a an error to the socket + private func writeError(code: UInt16) { + let buf = NSMutableData(capacity: sizeof(UInt16)) + var buffer = UnsafeMutablePointer(buf!.bytes) + buffer[0] = code.byteSwapped + dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) + } + ///used to write things to the stream in a + private func dequeueWrite(data: NSData, code: OpCode) { + if writeQueue == nil { + writeQueue = NSOperationQueue() + writeQueue!.maxConcurrentOperationCount = 1 + } + writeQueue!.addOperationWithBlock { + //stream isn't ready, let's wait + var tries = 0; + while self.outputStream == nil || !self.connected { + if(tries < 5) { + sleep(1); + } else { + break; + } + tries++; + } + if !self.connected { + return + } + var offset = 2 + UINT16_MAX + let bytes = UnsafeMutablePointer(data.bytes) + let dataLength = data.length + let frame = NSMutableData(capacity: dataLength + self.MaxFrameSize) + let buffer = UnsafeMutablePointer(frame!.mutableBytes) + buffer[0] = self.FinMask | code.rawValue + if dataLength < 126 { + buffer[1] = CUnsignedChar(dataLength) + } else if dataLength <= Int(UInt16.max) { + buffer[1] = 126 + var sizeBuffer = UnsafeMutablePointer((buffer+offset)) + sizeBuffer[0] = UInt16(dataLength).byteSwapped + offset += sizeof(UInt16) + } else { + buffer[1] = 127 + var sizeBuffer = UnsafeMutablePointer((buffer+offset)) + sizeBuffer[0] = UInt64(dataLength).byteSwapped + offset += sizeof(UInt64) + } + buffer[1] |= self.MaskMask + var maskKey = UnsafeMutablePointer(buffer + offset) + SecRandomCopyBytes(kSecRandomDefault, UInt(sizeof(UInt32)), maskKey) + offset += sizeof(UInt32) + + for (var i = 0; i < dataLength; i++) { + buffer[offset] = bytes[i] ^ maskKey[i % sizeof(UInt32)] + offset += 1 + } + var total = 0 + while true { + if self.outputStream == nil { + break + } + let writeBuffer = UnsafePointer(frame!.bytes+total) + var len = self.outputStream?.write(writeBuffer, maxLength: offset-total) + if len == nil || len! < 0 { + var error: NSError? + if let streamError = self.outputStream?.streamError { + error = streamError + } else { + let errCode = InternalErrorCode.OutputStreamWriteError.rawValue + error = self.errorWithDetail("output stream error during write", code: errCode) + } + if let disconnect = self.disconnectedBlock { + disconnect(error) + } + self.delegate?.websocketDidDisconnect(self, error: error) + break + } else { + total += len! + } + if total >= offset { + break + } + } + + } + } + +} From 711ce8d8c428516ec008f598f18281984f30290e Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 17:33:21 -0400 Subject: [PATCH 50/64] fixes. add better objective-c support --- README.md | 15 +++++++++++ SwiftIO/SocketAckHandler.swift | 4 +-- SwiftIO/SocketEngine.swift | 4 +-- SwiftIO/SocketEventHandler.swift | 6 ++--- SwiftIO/SocketIOClient.swift | 44 ++++++++++++++++++++------------ 5 files changed, 50 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 3ac0c44..45f6a01 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,21 @@ socket.emit("testData", [ "test": true]) ``` +Objective-C Example +=================== +```objective-c +SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8080" opts:nil]; + +[socket on: @"connect" callback: ^(NSArray* data, void (^ack)(NSArray*)) { + NSLog(@"connected"); + [socket emitObjc:@"echo" :@[@"echo test"]]; + [[socket emitWithAckObjc:@"ackack" :@[@"test"]] onAck:^(NSArray* data) { + NSLog(@"Got data"); + }]; +}]; + +``` + Detailed Example ================ A more detailed example can be found [here](https://github.com/nuclearace/socket.io-client-swift-example) diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index db4fe3f..bc506a0 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -24,9 +24,9 @@ import Foundation -typealias AckCallback = (NSArray?) -> Void +public typealias AckCallback = (NSArray?) -> Void -class SocketAckHandler { +@objc public class SocketAckHandler { let ackNum:Int! let event:String! var callback:AckCallback? diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 817097a..4aeabca 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -147,7 +147,8 @@ class SocketEngine: NSObject, WebSocketDelegate { return } - let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + req.timeoutInterval = 0.0 self.waitingForPoll = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in @@ -481,7 +482,6 @@ class SocketEngine: NSObject, WebSocketDelegate { func sendPing() { if self.websocket { - self.ws?.writePing(NSData()) self.sendWebSocketMessage("", withType: PacketType.PING) } else { self.sendPollMessage("", withType: PacketType.PING) diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 2294025..09f99b7 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -24,9 +24,9 @@ import Foundation -typealias NormalCallback = (NSArray?, AckEmitter?) -> Void -typealias AnyHandler = (event:String, items:AnyObject?) -typealias AckEmitter = (AnyObject...) -> Void +public typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +public typealias AnyHandler = (event:String, items:AnyObject?) +public typealias AckEmitter = (AnyObject...) -> Void private func emitAckCallback(socket:SocketIOClient, num:Int, type:Int) -> AckEmitter { func emitter(items:AnyObject...) { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 020b977..a61e838 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient { +public class SocketIOClient: NSObject { let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -57,7 +57,7 @@ class SocketIOClient { } var sid:String? - init(socketURL:String, opts:[String: AnyObject]? = nil) { + public init(socketURL:String, opts:[String: AnyObject]? = nil) { var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { @@ -96,11 +96,13 @@ class SocketIOClient { self.reconnectAttempts = -1 } + super.init() + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } // Closes the socket - func close() { + public func close() { self.closed = true self.connecting = false self.connected = false @@ -109,7 +111,7 @@ class SocketIOClient { } // Connects to the server - func connect() { + public func connect() { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") self.closed = false @@ -119,7 +121,7 @@ class SocketIOClient { } // Connect to the server using params - func connectWithParams(params:[String: AnyObject]) { + public func connectWithParams(params:[String: AnyObject]) { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") self.closed = false @@ -139,6 +141,7 @@ class SocketIOClient { self.currentReconnectAttempt = 0 self.reconnectTimer?.invalidate() self.reconnectTimer = nil + self.handleEvent("connect", data: nil, isInternalMessage: false) } @@ -155,7 +158,7 @@ class SocketIOClient { // Sends a message with multiple args // If a message contains binary we have to send those // seperately. - func emit(event:String, _ args:AnyObject...) { + public func emit(event:String, _ args:AnyObject...) { if !self.connected { return } @@ -166,7 +169,12 @@ class SocketIOClient { } } - func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { + // Objc doesn't have variadics + public func emitObjc(event:String, _ args:[AnyObject]) { + self.emit(event, args) + } + + public func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { if !self.connected { return SocketAckHandler(event: "fail") } @@ -183,6 +191,10 @@ class SocketIOClient { return ackHandler } + public func emitWithAckObjc(event:String, _ args:[AnyObject]) -> SocketAckHandler { + return self.emitWithAck(event, args) + } + private func _emit(event:String, _ args:[AnyObject], ack:Bool = false) { var frame:SocketEvent var str:String @@ -217,7 +229,7 @@ class SocketIOClient { } // If the server wants to know that the client received data - func emitAck(ack:Int, withData data:[AnyObject]?, withAckType ackType:Int) { + internal func emitAck(ack:Int, withData data:[AnyObject]?, withAckType ackType:Int) { dispatch_async(self.ackQueue) {[weak self] in if self == nil || !self!.connected || data == nil { return @@ -270,7 +282,7 @@ class SocketIOClient { } // Handles events - func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, + public func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { // println("Should do event: \(event) with data: \(data)") if !self.connected && !isInternalMessage { @@ -319,18 +331,18 @@ class SocketIOClient { } // Adds handler for an event - func on(name:String, callback:NormalCallback) { + public func on(name:String, callback:NormalCallback) { let handler = SocketEventHandler(event: name, callback: callback) self.handlers.append(handler) } // Adds a handler for any event - func onAny(handler:(AnyHandler) -> Void) { + public func onAny(handler:(AnyHandler) -> Void) { self.anyHandler = handler } // Opens the connection to the socket - func open() { + public func open() { self.connect() } @@ -497,7 +509,7 @@ class SocketIOClient { } // Parses messages recieved - func parseSocketMessage(stringMessage:String) { + internal func parseSocketMessage(stringMessage:String) { // println(message!) // Check for successful namepsace connect @@ -718,7 +730,7 @@ class SocketIOClient { } // Handles binary data - func parseBinaryData(data:NSData) { + internal func parseBinaryData(data:NSData) { let shouldExecute = self.waitingData[0].addData(data) if shouldExecute { @@ -763,7 +775,7 @@ class SocketIOClient { } // Something happened while polling - func pollingDidFail(err:NSError?) { + internal func pollingDidFail(err:NSError?) { if !self.reconnecting { self.connected = false self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) @@ -772,7 +784,7 @@ class SocketIOClient { } // We lost connection and should attempt to reestablish - @objc func tryReconnect() { + internal func tryReconnect() { if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { self.didForceClose() return From 8c02f5ef7b352c3db45f840f61271f77a4cab27d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 17:38:27 -0400 Subject: [PATCH 51/64] update readme with new methods --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 45f6a01..f7e2786 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,12 @@ Methods 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. 2. `socket.onAny(callback:((event:String, items:AnyObject?)) -> Void)` - Adds a handler for all events. It will be called on any received event. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. -5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. -6. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. -7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +4. `socket.emitObjc(event:String, args:[AnyObject])` - `emit` for Objective-C +5. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +6. `socket.emitWithAckObjc(event:String, _ args:[AnyObject]) -> SocketAckHandler` - `emitWithAck` for Objective-C. +7. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. +8. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. +9. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ From 114ce4ca0f74a9ad2772edd146a7f6af3b1a7483 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 17:42:51 -0400 Subject: [PATCH 52/64] trim example --- README.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/README.md b/README.md index f7e2786..16e1713 100644 --- a/README.md +++ b/README.md @@ -78,25 +78,6 @@ socket.on("ackEvent") {data, ack in ack?("Got your event", "dude") } -socket.on("disconnect") {data, ack in - if let reason = data?[0] as? String { - println("Socket disconnected: \(reason)") - } -} - -socket.on("reconnect") {data, ack in - if let reason = data?[0] as? String { - println("Socket reconnecting: \(reason)") - } -} - -socket.on("reconnectAttempt") {data, ack in - if let triesLeft = data?[0] as? Int { - println(triesLeft) - } -} -// End Socket Events - socket.on("jsonTest") {data, ack in if let json = data?[0] as? NSDictionary { println(json["test"]!) // foo bar @@ -122,22 +103,6 @@ socket.on("multipleItems") {data, ack in } } -// Recieving binary -socket.on("dataTest") {data, ack in - if let data = data?[0] as? NSData { - println("data is binary") - } -} - -socket.on("objectDataTest") {data, ack in - if let dict = data?[0] as? NSDictionary { - if let data = dict["data"] as? NSData { - let string = NSString(data: data, encoding: NSUTF8StringEncoding) - println("Got data: \(string!)") - } - } -} - // Connecting socket.connect() From 2e451e5185442a92317391a132e128d488bf61c7 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 18:05:25 -0400 Subject: [PATCH 53/64] trim more --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 16e1713..72e8f5b 100644 --- a/README.md +++ b/README.md @@ -105,12 +105,6 @@ socket.on("multipleItems") {data, ack in // Connecting socket.connect() - -// Sending binary -socket.emit("testData", [ - "data": "Hello World".dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)!, - "test": true]) ``` Objective-C Example From 33f3ef4846aecbc8199544282de91368e325e6fb Mon Sep 17 00:00:00 2001 From: banjun Date: Thu, 12 Mar 2015 08:07:37 +0900 Subject: [PATCH 54/64] add podspec for v1.1.0 and LICENSE --- LICENSE | 230 +++++++++++++++++++++++++++++++++ Socket.IO-Client-Swift.podspec | 17 +++ 2 files changed, 247 insertions(+) create mode 100644 LICENSE create mode 100644 Socket.IO-Client-Swift.podspec diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1274e18 --- /dev/null +++ b/LICENSE @@ -0,0 +1,230 @@ +The MIT License (MIT) + +Copyright (c) 2014-2015 Erik Little + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +This library makes use of the following third party libraries: + +Starscream +---------- + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec new file mode 100644 index 0000000..9731412 --- /dev/null +++ b/Socket.IO-Client-Swift.podspec @@ -0,0 +1,17 @@ +Pod::Spec.new do |s| + s.name = "Socket.IO-Client-Swift" + s.version = "1.1.0" + s.summary = "Socket.IO-client for Swift" + s.description = <<-DESC + Socket.IO-client for Swift. + Supports ws/wss/polling connections and binary. + For socket.io 1.0+ and Swift 1.1. + DESC + s.homepage = "https://github.com/socketio/socket.io-client-swift" + s.license = { :type => 'MIT' } + s.author = { "Erik" => "nuclear.ace@gmail.com" } + s.platform = :ios, "8.0" + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.0' } + s.source_files = "SwiftIO/**/*.swift" + # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files +end From 30cd0bd6cde5bb2a68400de2494663f2b3aa6e60 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 09:17:49 -0400 Subject: [PATCH 55/64] update thing --- Socket.IO-Client-Swift.podspec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 9731412..05ff25b 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -11,7 +11,8 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT' } s.author = { "Erik" => "nuclear.ace@gmail.com" } s.platform = :ios, "8.0" - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.0' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.0-pod' } s.source_files = "SwiftIO/**/*.swift" + s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files end From 8f7f43f83e11c5258c5cd9e63a8e6c618ccc6dfb Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 09:31:27 -0400 Subject: [PATCH 56/64] add osx --- Socket.IO-Client-Swift.podspec | 1 + 1 file changed, 1 insertion(+) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 05ff25b..9f9ed53 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -11,6 +11,7 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT' } s.author = { "Erik" => "nuclear.ace@gmail.com" } s.platform = :ios, "8.0" + s.platform = :osx, "10.10" s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.0-pod' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true From a84aa31489599b4519a611a6d84c3a5404d78f6f Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 09:32:12 -0400 Subject: [PATCH 57/64] update podspec --- Socket.IO-Client-Swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 9f9ed53..1291850 100644 --- a/Socket.IO-Client-Swift.podspec +++ b/Socket.IO-Client-Swift.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.author = { "Erik" => "nuclear.ace@gmail.com" } s.platform = :ios, "8.0" s.platform = :osx, "10.10" - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.0-pod' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.1' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files From 3e5fe4009c8809b798d82e983218735c8789eb81 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 09:33:59 -0400 Subject: [PATCH 58/64] Really update it --- Socket.IO-Client-Swift.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 1291850..7c2f910 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 = "1.1.0" + s.version = "1.1.1" s.summary = "Socket.IO-client for Swift" s.description = <<-DESC Socket.IO-client for Swift. From ea20dc809e5ea7719e3b259629ad5e21711b2f8c Mon Sep 17 00:00:00 2001 From: banjun Date: Thu, 12 Mar 2015 22:54:43 +0900 Subject: [PATCH 59/64] [README] add CocoaPods in Installation section --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 72e8f5b..5d0948f 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,42 @@ For Swift 1.2 use the 1.2 branch. Installation ============ + +Manually (iOS 7+) +----------------- 1. Copy the SwiftIO folder into your Xcode project! +CocoaPods (iOS 8+) +------------------ +Install cocoapods 0.36.0 or later: + +``` +$ gem install cocoapods +``` + +Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`: + +```ruby +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +use_frameworks! + +pod 'Socket.IO-Client-Swift', '~> 1.1' +``` + +Install pods: + +``` +$ pod install +``` + +Import in your swift file: + +```swift +import Socket_IO_Client_Swift +``` + + API === Constructor From abbd8e6a67b62383bfd1a97d495cd776a5b74d73 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 10:19:59 -0400 Subject: [PATCH 60/64] update --- Socket.IO-Client-Swift.podspec | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 7c2f910..1a728c8 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 = "1.1.1" + s.version = "1.1.2" s.summary = "Socket.IO-client for Swift" s.description = <<-DESC Socket.IO-client for Swift. @@ -10,9 +10,9 @@ Pod::Spec.new do |s| s.homepage = "https://github.com/socketio/socket.io-client-swift" s.license = { :type => 'MIT' } s.author = { "Erik" => "nuclear.ace@gmail.com" } - s.platform = :ios, "8.0" - s.platform = :osx, "10.10" - s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.1' } + 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 => 'v1.1.2' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files From 58362f6352d7f63f530305522f5b18898950e3df Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 10:27:37 -0400 Subject: [PATCH 61/64] clean up readme --- README.md | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 5d0948f..63b0744 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,8 @@ Manually (iOS 7+) ----------------- 1. Copy the SwiftIO folder into your Xcode project! -CocoaPods (iOS 8+) +CocoaPods 0.36.0 or later (iOS 8+) ------------------ -Install cocoapods 0.36.0 or later: - -``` -$ gem install cocoapods -``` - Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`: ```ruby @@ -42,7 +36,6 @@ Import in your swift file: import Socket_IO_Client_Swift ``` - API === Constructor @@ -105,6 +98,11 @@ socket.on("ackEvent") {data, ack in println("Got ackEvent") } + // data is an array + if let int = data?[1] as? Int { + println("Got int") + } + socket.emitWithAck("ackTest", "test").onAck {data in println(data?[0]) } @@ -118,25 +116,6 @@ socket.on("jsonTest") {data, ack in } } -// Event items are passed by an array -socket.on("multipleItems") {data, ack in - if data == nil { - return - } - - if let str = data![0] as? String { - println(str) - } - - if let arr = data![1] as? [Int] { - println(arr) - } - - if let obj = data![4] as? NSDictionary { - println(obj["test"]) - } -} - // Connecting socket.connect() ``` From ab5cccce8571bf5681cff1f7c7b6a918bd99fbe8 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 11:38:45 -0400 Subject: [PATCH 62/64] fix binary in dictionaries --- Socket.IO-Client-Swift.podspec | 4 ++-- SwiftIO/SocketIOClient.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 1a728c8..dc311d7 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 = "1.1.2" + s.version = "1.1.3" s.summary = "Socket.IO-client for Swift" s.description = <<-DESC Socket.IO-client for Swift. @@ -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 => 'v1.1.2' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.1.3' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index a61e838..9694c2f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -476,7 +476,7 @@ public class SocketIOClient: NSObject { currentPlaceholder++ hasBinary = true returnDatas.append(binaryData) - returnDict[key as String] = ["_placeholder": true, "num": currentPlaceholder++] + returnDict[key as String] = ["_placeholder": true, "num": currentPlaceholder] } else if let arr = value as? NSArray { let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) From 74939f1ceae1e177d1267de05b1ffbb35638fe33 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 13:38:06 -0400 Subject: [PATCH 63/64] fixes --- SwiftIO/SocketEngine.swift | 128 ++++++++++++++++------------- SwiftIO/SocketIOClient.swift | 152 +++++++++++++++++------------------ 2 files changed, 145 insertions(+), 135 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 4aeabca..f7555f3 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -24,10 +24,6 @@ import Foundation -// This is used because in Swift 1.1, turning on -O causes a -// memory access violation in SocketEngine#parseEngineMessage -private var fixSwift:AnyObject? - extension String { private var length:Int { return countElements(self) @@ -55,18 +51,22 @@ class SocketEngine: NSObject, WebSocketDelegate { "parseQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private let session:NSURLSession! + private var _connected = false + private var fastUpgrade = false private var forcePolling = false private var pingTimer:NSTimer? private var postWait = [String]() private var _polling = true private var probing = false private var probeWait = PollWaitQueue() - private let session:NSURLSession! private var waitingForPoll = false private var waitingForPost = false private var _websocket = false private var websocketConnected = false - var connected = false + var connected:Bool { + return self._connected + } var pingInterval:Int? var polling:Bool { return self._polling @@ -142,14 +142,29 @@ class SocketEngine: NSObject, WebSocketDelegate { return (urlPolling, urlWebSocket) } + private func doFastUpgrade() { + self.sendWebSocketMessage("", withType: PacketType.UPGRADE) + self._websocket = true + self._polling = false + self.fastUpgrade = false + self.flushProbeWait() + } + private func doPoll() { - if self.urlPolling == nil || self.websocket || self.waitingForPoll || !self.connected { + if self.websocket || self.waitingForPoll || !self.connected { return } - let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - req.timeoutInterval = 0.0 self.waitingForPoll = true + self.doRequest(self.parsePollingMessage) + } + + private func doRequest(callback:(String) -> Void) { + if !self.polling { + return + } + + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { @@ -158,29 +173,31 @@ class SocketEngine: NSObject, WebSocketDelegate { if self!.polling { self?.handlePollingFailed(err) } + return } - // println(data) if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { // println(str) - dispatch_async(self!.parseQueue) {[weak self] in - if self == nil { - return - } - - self?.parsePollingMessage(str) - return - } + + dispatch_async(self!.parseQueue) {callback(str)} } self?.waitingForPoll = false - self?.doPoll() + + if self!.fastUpgrade { + self?.doFastUpgrade() + return + } else { + self?.doPoll() + } }.resume() } + + private func flushProbeWait() { // println("flushing probe wait") dispatch_async(self.emitQueue) {[weak self] in @@ -227,7 +244,6 @@ class SocketEngine: NSObject, WebSocketDelegate { req.HTTPBody = postData self.waitingForPost = true - self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return @@ -255,10 +271,11 @@ class SocketEngine: NSObject, WebSocketDelegate { } // A poll failed, tell the client about it - // We check to see if we were closed by the server first private func handlePollingFailed(reason:NSError?) { + assert(self.polling, "Polling failed when we're not polling") + if !self.client.reconnecting { - self.connected = false + self._connected = false self.ws?.disconnect() self.pingTimer?.invalidate() self.waitingForPoll = false @@ -268,7 +285,7 @@ class SocketEngine: NSObject, WebSocketDelegate { } func open(opts:[String: AnyObject]? = nil) { - if self.waitingForPost || self.waitingForPoll || self.websocket || self.connected { + if self.connected { assert(false, "We're in a bad state, this shouldn't happen.") } @@ -304,13 +321,12 @@ class SocketEngine: NSObject, WebSocketDelegate { return } - self?.connected = true - if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.AllowFragments, error: &err2) as? NSDictionary { if let sid = json["sid"] as? String { // println(json) self?.sid = sid + self?._connected = true if !self!.forcePolling { self?.ws = WebSocket(url: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) @@ -363,7 +379,7 @@ class SocketEngine: NSObject, WebSocketDelegate { length += chr } else { if length == "" || testLength(length, &n) { - self.handlePollingFailed(nil) + println("failure in parsePollingMessage") return } @@ -379,10 +395,8 @@ class SocketEngine: NSObject, WebSocketDelegate { if msg.length != 0 { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in - fixSwift = msg - if fixSwift is String { - self?.parseEngineMessage(fixSwift as String) - } + self?.parseEngineMessage(msg) + return } } @@ -462,7 +476,7 @@ class SocketEngine: NSObject, WebSocketDelegate { self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { // println("sending poll: \(msg):\(datas)") - self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) + self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas, doPoll: true) } } } @@ -484,34 +498,34 @@ class SocketEngine: NSObject, WebSocketDelegate { if self.websocket { self.sendWebSocketMessage("", withType: PacketType.PING) } else { - self.sendPollMessage("", withType: PacketType.PING) + self.sendPollMessage("", withType: PacketType.PING, doPoll: false) } } - private func sendPollMessage(msg:String, withType type:PacketType, datas:[NSData]? = nil) { - // println("Sending: poll: \(msg) as type: \(type.rawValue)") - let strMsg = "\(type.rawValue)\(msg)" - - self.postWait.append(strMsg) - - if datas != nil { - for data in datas! { - let (nilData, b64Data) = self.createBinaryDataForSend(data) - - self.postWait.append(b64Data!) + private func sendPollMessage(msg:String, withType type:PacketType, + datas:[NSData]? = nil, doPoll poll:Bool) { + // println("Sending poll: \(msg) as type: \(type.rawValue)") + let strMsg = "\(type.rawValue)\(msg)" + + self.postWait.append(strMsg) + + if datas != nil { + for data in datas! { + let (nilData, b64Data) = self.createBinaryDataForSend(data) + + self.postWait.append(b64Data!) + } + } + + if !self.waitingForPoll && self.waitingForPost && poll { + self.doPoll() + } else { + self.flushWaitingForPost() } - } - - if waitingForPost { - self.doPoll() - return - } else { - self.flushWaitingForPost() - } } private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { - // println("Sending: ws: \(str) as type: \(type.rawValue)") + // println("Sending ws: \(str) as type: \(type.rawValue)") self.ws?.writeString("\(type.rawValue)\(str)") if datas != nil { @@ -539,12 +553,10 @@ class SocketEngine: NSObject, WebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + // Do a fast upgrade + self.fastUpgrade = true self.probing = false - self._websocket = true - self.waitingForPoll = false - self._polling = false - self.sendWebSocketMessage("", withType: PacketType.UPGRADE) - self.flushProbeWait() + self.sendPollMessage("", withType: PacketType.NOOP, doPoll: false) } } @@ -560,7 +572,7 @@ class SocketEngine: NSObject, WebSocketDelegate { if self.websocket { self.pingTimer?.invalidate() - self.connected = false + self._connected = false self._websocket = false self._polling = true self.client.webSocketDidCloseWithCode(1, reason: "Socket Disconnect", wasClean: true) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 9694c2f..fd61ba7 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -173,7 +173,7 @@ public class SocketIOClient: NSObject { public func emitObjc(event:String, _ args:[AnyObject]) { self.emit(event, args) } - + public func emitWithAck(event:String, _ args:AnyObject...) -> SocketAckHandler { if !self.connected { return SocketAckHandler(event: "fail") @@ -643,90 +643,88 @@ public class SocketIOClient: NSObject { } // Tries to parse a message that contains binary - private func parseBinaryMessage(#message:AnyObject) { + private func parseBinaryMessage(#message:String) { // println(message) - if let stringMessage = message as? String { - var mutMessage = RegexMutable(stringMessage) + var mutMessage = RegexMutable(message) + + /** + Begin check for binary placeholders + **/ + let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$"].groups() + + if binaryGroup == nil { + return + } + + if binaryGroup[1].hasPrefix("5") { + // println(binaryGroup) + var ackNum:String + var event:String + var mutMessageObject:NSMutableString + var namespace:String? + var numberOfPlaceholders:String + let messageType = RegexMutable(binaryGroup[1]) - /** - Begin check for binary placeholders - **/ - let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$"].groups() + namespace = binaryGroup[2] + if binaryGroup[3] != "" { + ackNum = binaryGroup[3] as String + } else if self.nsp == nil && binaryGroup[2] != "" { + ackNum = binaryGroup[2] + } else { + ackNum = "" + } - if binaryGroup == nil { + numberOfPlaceholders = (messageType["5"] ~= "") as String + event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String + mutMessageObject = RegexMutable(binaryGroup[5]) + + if namespace == "" && self.nsp != nil { return } - if binaryGroup[1].hasPrefix("5") { - // println(binaryGroup) - var ackNum:String - var event:String - var mutMessageObject:NSMutableString - var namespace:String? - var numberOfPlaceholders:String - let messageType = RegexMutable(binaryGroup[1]) - - namespace = binaryGroup[2] - if binaryGroup[3] != "" { - ackNum = binaryGroup[3] as String - } else if self.nsp == nil && binaryGroup[2] != "" { - ackNum = binaryGroup[2] - } else { - ackNum = "" - } - - numberOfPlaceholders = (messageType["5"] ~= "") as String - event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String - mutMessageObject = RegexMutable(binaryGroup[5]) - - if namespace == "" && self.nsp != nil { - return - } - - let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] - ~= "\"~~$2\"" - - var mes:SocketEvent - if ackNum == "" { - mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.toInt()!) - } else { - self.currentAck = ackNum.toInt()! - mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) - } - - self.waitingData.append(mes) - } else if binaryGroup[1].hasPrefix("6") { - let messageType = RegexMutable(binaryGroup[1]) - let numberOfPlaceholders = (messageType["6"] ~= "") as String - var ackNum:String - var nsp:String - - if binaryGroup[3] == "" { - ackNum = binaryGroup[2] - nsp = "" - } else { - ackNum = binaryGroup[3] - nsp = binaryGroup[2] - } - - if nsp == "" && self.nsp != nil { - return - } - var mutMessageObject = RegexMutable(binaryGroup[5]) - let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] - ~= "\"~~$2\"" - - let event = SocketEvent(event: "", args: placeholdersRemoved, - placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) - - self.waitingData.append(event) + let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] + ~= "\"~~$2\"" + + var mes:SocketEvent + if ackNum == "" { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!) + } else { + self.currentAck = ackNum.toInt()! + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) } - /** - End check for binary placeholders - **/ + + self.waitingData.append(mes) + } else if binaryGroup[1].hasPrefix("6") { + let messageType = RegexMutable(binaryGroup[1]) + let numberOfPlaceholders = (messageType["6"] ~= "") as String + var ackNum:String + var nsp:String + + if binaryGroup[3] == "" { + ackNum = binaryGroup[2] + nsp = "" + } else { + ackNum = binaryGroup[3] + nsp = binaryGroup[2] + } + + if nsp == "" && self.nsp != nil { + return + } + var mutMessageObject = RegexMutable(binaryGroup[5]) + let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] + ~= "\"~~$2\"" + + let event = SocketEvent(event: "", args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) + + self.waitingData.append(event) } + /** + End check for binary placeholders + **/ } // Handles binary data From 8e7c8444d3746c6311839bffd30c0dd287d88c6a Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 12 Mar 2015 13:44:55 -0400 Subject: [PATCH 64/64] set socket sid --- SwiftIO/SocketIOClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index fd61ba7..6be1253 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -141,6 +141,7 @@ public class SocketIOClient: NSObject { self.currentReconnectAttempt = 0 self.reconnectTimer?.invalidate() self.reconnectTimer = nil + self.sid = self.engine?.sid self.handleEvent("connect", data: nil, isInternalMessage: false) }