From ca00926647b1e2c7ebb40719e8d0d2ff4aa288f9 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 9 Feb 2015 17:06:45 -0500 Subject: [PATCH 01/42] swift 1.2 --- SwiftIO/SocketEvent.swift | 16 ++++---- SwiftIO/SocketEventHandler.swift | 2 + SwiftIO/SocketIOClient.swift | 30 +++++++------- SwiftIO/SwiftRegex.swift | 68 ++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 52 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 679044b..729b959 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -33,7 +33,7 @@ class SocketEvent { init(event:String, args:AnyObject?, placeholders:Int = 0) { self.event = event - self.args = args? + self.args = args self.placeholders = placeholders } @@ -89,7 +89,7 @@ class SocketEvent { options: NSJSONWritingOptions(0), error: &jsonSendError) let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) - message += jsonString! + message += jsonString! as! String continue } @@ -132,7 +132,7 @@ class SocketEvent { var newDict = [String: AnyObject]() for (key, value) in dict { - newDict[key as String] = value + newDict[key as! String] = value // If the value is a string we need to check // if it is a placeholder for data @@ -140,14 +140,14 @@ class SocketEvent { let mut = RegexMutable(str) if let num = mut["~~(\\d)"].groups() { - newDict[key as String] = self.datas[num[1].toInt()!] + newDict[key as! String] = self.datas[num[1].toInt()!] } else { - newDict[key as String] = str + newDict[key as! String] = str } } else if let nestDict = value as? NSDictionary { - newDict[key as String] = self.fillInDict(nestDict) + newDict[key as! String] = self.fillInDict(nestDict) } else if let arr = value as? NSArray { - newDict[key as String] = self.fillInArray(arr) + newDict[key as! String] = self.fillInArray(arr) } } @@ -158,7 +158,7 @@ class SocketEvent { if let dict = args as? NSDictionary { return self.fillInDict(dict) } else if let arr = args as? NSArray { - return self.fillInArray(args as NSArray) + return self.fillInArray(args as! NSArray) } else if let string = args as? String { if string == "~~\(self.currentPlace)" { return self.datas[0] diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 8a77b1f..782396e 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -31,11 +31,13 @@ class SocketEventHandler { init(event:String, callback:NormalCallback) { self.event = event self.callback = callback + self.callbackMult = nil } init(event:String, callback:MultipleCallback) { self.event = event self.callbackMult = callback + self.callback = nil self.multiEvent = true } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8c607f4..55e688a 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -28,8 +28,8 @@ typealias NormalCallback = (AnyObject?) -> Void typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { - let socketURL:String! - private let secure:Bool! + let socketURL:NSMutableString! + private var secure = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var pingTimer:NSTimer! @@ -45,7 +45,6 @@ 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 { @@ -55,6 +54,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" + self.socketURL = mutURL // Set options @@ -96,7 +96,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = false var endpoint:String - if self.secure! { + if self.secure { endpoint = "wss://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" } else { endpoint = "ws://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" @@ -197,7 +197,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { for handler in self.handlers { if handler.event == event { if data is NSArray { - handler.executeCallback(nil, items: (data as NSArray)) + handler.executeCallback(nil, items: (data as! NSArray)) } else { handler.executeCallback(data) } @@ -241,7 +241,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { for g in 0..() +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]! { @@ -95,7 +95,7 @@ public class SwiftRegex: NSObject, BooleanType { if let mutableTarget = target as? NSMutableString { 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 { @@ -105,7 +105,7 @@ public class SwiftRegex: NSObject, BooleanType { } func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult] { - return regex.matchesInString(target, options: options, range: targetRange) as [NSTextCheckingResult] + return regex.matchesInString(target as! String, options: options, range: targetRange) as! [NSTextCheckingResult] } public func ranges(options: NSMatchingOptions = nil) -> [NSRange] { @@ -113,7 +113,14 @@ public class SwiftRegex: NSObject, BooleanType { } public func matches(options: NSMatchingOptions = nil) -> [String] { - return matchResults(options: options).map { self.substring($0.range) } + var returnStr = [String]() + var t = matchResults(options: options).map { self.substring($0.range) } + + for str in t { + returnStr.append(str as! String) + } + + return returnStr } public func allGroups(options: NSMatchingOptions = nil) -> [[String]] { @@ -123,8 +130,8 @@ public class SwiftRegex: NSObject, BooleanType { public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { var out = Dictionary() for match in matchResults(options: options) { - out[substring(match.rangeAtIndex(1))] = - substring(match.rangeAtIndex(2)) + out[substring(match.rangeAtIndex(1)) as! String] = + substring(match.rangeAtIndex(2)) as? String } return out } @@ -134,19 +141,24 @@ 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) + var str = "" + for char in out as String { + str += String(char) + } + + mutableTarget.setString(str) return mutableTarget } else { SwiftRegex.failure("Modify on non-mutable") @@ -208,43 +220,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 6d6ac0fc741e28e12424d2b3056807c8ce050c63 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Feb 2015 11:56:14 -0500 Subject: [PATCH 02/42] clean up swiftregex --- SwiftIO/SwiftRegex.swift | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 6f4db40..68396a7 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -113,14 +113,7 @@ public class SwiftRegex: NSObject, BooleanType { } public func matches(options: NSMatchingOptions = nil) -> [String] { - var returnStr = [String]() - var t = matchResults(options: options).map { self.substring($0.range) } - - for str in t { - returnStr.append(str as! String) - } - - return returnStr + return matchResults(options: options).map { self.substring($0.range) } as [NSString] as! [String] } public func allGroups(options: NSMatchingOptions = nil) -> [[String]] { @@ -153,12 +146,7 @@ public class SwiftRegex: NSObject, BooleanType { out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) as! String ) if let mutableTarget = target as? NSMutableString { - var str = "" - for char in out as String { - str += String(char) - } - - mutableTarget.setString(str) + mutableTarget.setString(out as String) return mutableTarget } else { SwiftRegex.failure("Modify on non-mutable") From b53d22f3a2c660ad4e7169db425a559ad7be60b9 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 13 Feb 2015 17:07:04 -0500 Subject: [PATCH 03/42] Use dispatch queues for buffering emitting and recieving --- SwiftIO/SocketIOClient.swift | 71 +++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 55e688a..3b60b73 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -29,6 +29,10 @@ typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { let socketURL:NSMutableString! + let handleQueue = dispatch_queue_create("handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) + let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), + DISPATCH_QUEUE_SERIAL) private var secure = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? @@ -124,6 +128,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } + dispatch_async(self.emitQueue) {self._emit(event, args)} + } + + private func _emit(event:String, _ args:[AnyObject]) { var frame:SocketEvent var str:String var items = [AnyObject](count: args.count, repeatedValue: 1) @@ -175,6 +183,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } + if !self.connected { + return + } + if hasBinary { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp) @@ -191,15 +203,23 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Handles events - func handleEvent(#event:String, data:AnyObject?, multipleItems:Bool = false) { + func handleEvent(event:String, data:AnyObject?, multipleItems:Bool = false, internalMessage:Bool = false) { // println("Should do event: \(event) with data: \(data)") + if !self.connected && !internalMessage { + return + } for handler in self.handlers { if handler.event == event { if data is NSArray { - handler.executeCallback(nil, items: (data as! NSArray)) + dispatch_async(dispatch_get_main_queue()) { + handler.executeCallback(nil, items: (data as! NSArray)) + } + } else { - handler.executeCallback(data) + dispatch_async(dispatch_get_main_queue()) { + handler.executeCallback(data) + } } } } @@ -354,7 +374,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 } } @@ -413,7 +433,7 @@ 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) + self.handleEvent(event, data: parsed) return } else if let strData = data { // There are multiple items in the message @@ -421,7 +441,7 @@ 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) + self.handleEvent(event, data: parsed, multipleItems: true) return } } @@ -431,7 +451,7 @@ 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) + self.handleEvent(event, data: nil, multipleItems: false) return } } @@ -467,7 +487,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if binaryGroup != nil { // println(binaryGroup) - var event:NSString! + var event:String! var mutMessageObject:NSMutableString! var namespace:String? let messageType = RegexMutable(binaryGroup[1]) @@ -476,7 +496,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Check if message came from a namespace if binaryGroup.count == 5 { namespace = binaryGroup[2] - event = RegexMutable(binaryGroup[3])["\""] ~= "" + event = (RegexMutable(binaryGroup[3])["\""] ~= "") as String mutMessageObject = RegexMutable(binaryGroup[4]) } @@ -488,7 +508,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes = SocketEvent(event: event as! String, args: placeholdersRemoved, + let mes = SocketEvent(event: event, args: placeholdersRemoved, placeholders: numberOfPlaceholders.integerValue) self.lastSocketMessage = mes } @@ -508,10 +528,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) - self.handleEvent(event: event, data: filledInArgs) + self.handleEvent(event, data: filledInArgs) } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() - self.handleEvent(event: event, data: filledInArgs, multipleItems: true) + self.handleEvent(event, data: filledInArgs, multipleItems: true) return } } @@ -535,7 +555,8 @@ 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", + multipleItems: false, internalMessage: true) return } else if self.connected { self.connecting = false @@ -544,7 +565,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent(event: "reconnectAttempt", data: triesLeft) + self.handleEvent("reconnectAttempt", data: triesLeft, + multipleItems: false, internalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -567,7 +589,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - self.parseSocketMessage(message) + dispatch_async(self.handleQueue) {self.parseSocketMessage(message)} } // Called when the socket is opened @@ -583,7 +605,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 +616,11 @@ 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, + multipleItems: false, internalMessage: true) } else { - self.handleEvent(event: "reconnect", data: reason) + self.handleEvent("reconnect", data: reason, + multipleItems: false, internalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } @@ -605,11 +631,14 @@ 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, + multipleItems: false, internalMessage: true) if self.closed || !self.reconnects { - self.handleEvent(event: "disconnect", data: error.localizedDescription) + self.handleEvent("disconnect", data: error.localizedDescription, + multipleItems: false, internalMessage: true) } else if !self.reconnecting { - self.handleEvent(event: "reconnect", data: error.localizedDescription) + self.handleEvent("reconnect", data: error.localizedDescription, + multipleItems: false, internalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } From f826ca4b8e8fd3576d5f7c8e9ed915b858132912 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 13 Feb 2015 18:16:09 -0500 Subject: [PATCH 04/42] Fix weird optimization bug --- SwiftIO/SwiftRegex.swift | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 68396a7..360d051 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -93,7 +93,7 @@ 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 as! String, offset: 0, template: newValue ) mutableTarget.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) @@ -104,25 +104,32 @@ public class SwiftRegex: NSObject, BooleanType { } } - func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult] { - return regex.matchesInString(target as! String, 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) } as [NSString] as! [String] + 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) { + for match in matchResults(options: options)! { out[substring(match.rangeAtIndex(1)) as! String] = substring(match.rangeAtIndex(2)) as? String } From 5f179ac278d26837fb2480c824e324b5031d9658 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 13 Feb 2015 23:37:06 -0500 Subject: [PATCH 05/42] don't async handlers, change ioURL --- SwiftIO/SocketIOClient.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 3b60b73..45e5582 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -58,7 +58,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" - + self.socketURL = mutURL // Set options @@ -101,9 +101,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var endpoint:String if self.secure { - endpoint = "wss://\(self.socketURL)/socket.io/?EIO=2&transport=websocket" + 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)) @@ -212,14 +212,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { for handler in self.handlers { if handler.event == event { if data is NSArray { - dispatch_async(dispatch_get_main_queue()) { - handler.executeCallback(nil, items: (data as! NSArray)) - } + handler.executeCallback(nil, items: (data as! NSArray)) } else { - dispatch_async(dispatch_get_main_queue()) { - handler.executeCallback(data) - } + handler.executeCallback(data) } } } From c88febc5bccc2260327185bfef4db8ea07c2f121 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 14 Feb 2015 00:11:13 -0500 Subject: [PATCH 06/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2309504..ada1102 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.2 Installation ============ From 216d8cae1e536a253d47f4233800d0ddcd8f6a86 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 14 Feb 2015 17:36:34 -0500 Subject: [PATCH 07/42] Add acks, still needs more testing --- SwiftIO/SocketAckHandler.swift | 45 +++++ SwiftIO/SocketEvent.swift | 83 ++++++-- SwiftIO/SocketEventHandler.swift | 7 +- SwiftIO/SocketIOClient.swift | 333 +++++++++++++++++++++---------- 4 files changed, 337 insertions(+), 131 deletions(-) create mode 100644 SwiftIO/SocketAckHandler.swift diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift new file mode 100644 index 0000000..c6bed5a --- /dev/null +++ b/SwiftIO/SocketAckHandler.swift @@ -0,0 +1,45 @@ +// +// 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 event:String! + var ackData:[AnyObject]? + var callback:AckCallback? + + init(event:String) { + 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 729b959..b694de2 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -25,16 +25,18 @@ import Foundation class SocketEvent { + 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, ack:Int? = nil) { self.event = event self.args = args self.placeholders = placeholders + self.ack = ack } func addData(data:NSData) -> Bool { @@ -61,7 +63,7 @@ class SocketEvent { } } - class func createMessageForEvent(event:String, withArgs args:[AnyObject], + static func createMessageForEvent(event:String, withArgs args:[AnyObject], hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?) -> String { var message:String @@ -69,39 +71,78 @@ class SocketEvent { if !hasBinary { if nsp == nil { - message = "42[\"\(event)\"" + message = "42[\"\(event)\"," } else { - message = "42/\(nsp!),[\"\(event)\"" + message = "42/\(nsp!),[\"\(event)\"," } } else { if nsp == nil { - message = "45\(datas)-[\"\(event)\"" + message = "45\(datas)-[\"\(event)\"," } else { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + message = "45\(datas)-/\(nsp!),[\"\(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) + } + + static 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! as! String - continue + return self.completeMessage(msg, args: args) + + } else { + msg = "43\(nsp)[" + + return self.completeMessage(msg, args: args) } - - if arg is String { - message += "\"\(arg)\"" - continue + } else { + if nsp == "/" { + msg = "46\(binary)-\(ack)[\"\(event)\"" + + return self.completeMessage(msg, args: args) + + } else { + msg = "46\(binary)-\(nsp),\(ack)[\"\(event)\"" + + return self.completeMessage(msg, args: args) } + } + } + + private static 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 782396e..a13add6 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,22 +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 45e5582..2fdf57b 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -29,10 +29,13 @@ typealias MultipleCallback = (NSArray?) -> Void class SocketIOClient: NSObject, SRWebSocketDelegate { 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 secure = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? @@ -123,65 +126,30 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Sends a message with multiple args // If a message contains binary we have to send those // seperately. - func emit(event:String, _ args:AnyObject...) { + func emit(event:String, _ args:AnyObject...) -> SocketAckHandler { if !self.connected { - return + return SocketAckHandler(event: "fail") } - dispatch_async(self.emitQueue) {self._emit(event, args)} + let ackHandler = SocketAckHandler(event: event) + self.ackHandlers.append(ackHandler) + + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + self?._emit(event, args) + } + + return ackHandler } private func _emit(event:String, _ args:[AnyObject]) { 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 @@ -294,7 +309,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses data for events - class func parseData(data:String?) -> AnyObject? { + static func parseData(data:String?) -> AnyObject? { if data == nil { return nil } @@ -312,8 +327,61 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return parsed } + private static 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]) { + private static func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false if placeholders == -1 { @@ -400,17 +468,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 } @@ -429,7 +505,12 @@ 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, data: parsed) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + 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 @@ -437,7 +518,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // parseData to try and get an array. let asArray = "[\(strData)]" if let parsed:AnyObject = SocketIOClient.parseData(asArray) { - self.handleEvent(event, data: parsed, multipleItems: true) + if ackNum == "" { + self.handleEvent(event, data: parsed) + } else { + self.handleEvent(event, data: parsed, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -447,7 +533,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let noItemMessage = RegexMutable(messagePart)["\\[\"(.*?)\"]$"].groups() if noItemMessage != nil && noItemMessage.count == 2 { let event = noItemMessage[1] - self.handleEvent(event, data: nil, multipleItems: false) + if ackNum == "" { + self.handleEvent(event, data: nil) + } else { + self.handleEvent(event, data: nil, isInternalMessage: false, + wantsAck: ackNum.toInt(), withAckType: 3) + } return } } @@ -479,23 +570,30 @@ 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 { // 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])["\""] ~= "") as String - 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 @@ -504,8 +602,15 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.integerValue) + let mes:SocketEvent + if ackNum != "" { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!) + } else { + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ack: ackNum.toInt()) + } + self.lastSocketMessage = mes } /** @@ -524,11 +629,22 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) - self.handleEvent(event, data: filledInArgs) + + 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, data: filledInArgs, multipleItems: true) - 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) + } } } } @@ -541,8 +657,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 @@ -551,8 +669,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connecting = false self.reconnects = false self.reconnecting = false - self.handleEvent("disconnect", data: "Failed to reconnect", - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) return } else if self.connected { self.connecting = false @@ -561,8 +678,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -585,7 +701,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - dispatch_async(self.handleQueue) {self.parseSocketMessage(message)} + dispatch_async(self.handleQueue) {[weak self] in + if self == nil { + return + } + + self?.parseSocketMessage(message) + } } // Called when the socket is opened @@ -612,11 +734,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: reason, - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: reason, isInternalMessage: true) } else { - self.handleEvent("reconnect", data: reason, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } @@ -627,14 +747,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.connected = false self.connecting = false - self.handleEvent("error", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) } else if !self.reconnecting { - self.handleEvent("reconnect", data: error.localizedDescription, - multipleItems: false, internalMessage: true) + self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } From 308983c6c7f53ef952cc88428519312c1f82f8cf Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 14 Feb 2015 18:12:57 -0500 Subject: [PATCH 08/42] fix binary ack --- SwiftIO/SocketEvent.swift | 6 +++--- SwiftIO/SocketIOClient.swift | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index b694de2..3f90746 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -97,18 +97,18 @@ class SocketEvent { return self.completeMessage(msg, args: args) } else { - msg = "43\(nsp)[" + msg = "43/\(nsp),\(ack)[" return self.completeMessage(msg, args: args) } } else { if nsp == "/" { - msg = "46\(binary)-\(ack)[\"\(event)\"" + msg = "46\(binary)-\(ack)[" return self.completeMessage(msg, args: args) } else { - msg = "46\(binary)-\(nsp),\(ack)[\"\(event)\"" + msg = "46\(binary)-/\(nsp),\(ack)[" return self.completeMessage(msg, args: args) } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 2fdf57b..a8e044c 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -182,17 +182,21 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if !hasBinary { if self?.nsp == nil { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, withAckType: 3, withNsp: "/") + str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + withAckType: 3, withNsp: "/") } else { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, withAckType: 3, withNsp: self!.nsp!) + str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + withAckType: 3, withNsp: self!.nsp!) } self?.io?.send(str) } else { if self?.nsp == nil { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, withAckType: 6, withNsp: "/") + str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + withAckType: 6, withNsp: "/", withBinary: emitDatas.count) } else { - str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, withAckType: 6, withNsp: self!.nsp!) + str = SocketEvent.createAck(ack, withEvent: event, withArgs: items, + withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } self?.io?.send(str) @@ -603,7 +607,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { ~= "\"~~$2\"" let mes:SocketEvent - if ackNum != "" { + if ackNum == "" { mes = SocketEvent(event: event, args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!) } else { From 12b8f3ef1fd57f7d33c38411f5a2ee15ff810ee0 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 14 Feb 2015 18:22:24 -0500 Subject: [PATCH 09/42] Update README.md --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ada1102..770ff64 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ 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. -3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. +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...) -> SocketAckHandler` - Sends a message. Can send multiple args. Returns a SocketAckHandler that can be used to request an ack. See example. 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. @@ -30,6 +30,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: [ @@ -55,6 +57,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.emit("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)") From 3754bd572a749ac246ec85df295de9b500e75760 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 15 Feb 2015 13:17:36 -0500 Subject: [PATCH 10/42] handle acks from the server --- README.md | 7 +- SwiftIO/SocketAckHandler.swift | 6 +- SwiftIO/SocketEvent.swift | 32 +++++++-- SwiftIO/SocketIOClient.swift | 127 +++++++++++++++++++++++++++++---- 4 files changed, 145 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 770ff64..834cf24 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,10 @@ 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. -3. `socket.emit(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message. Can send multiple args. Returns a SocketAckHandler that can be used to request an ack. See example. -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. +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. Events ------ diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index c6bed5a..d3bfe77 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -24,14 +24,16 @@ import Foundation -typealias AckCallback = ([AnyObject]?) -> Void +typealias AckCallback = (AnyObject?) -> Void class SocketAckHandler { + let ackNum:Int! let event:String! var ackData:[AnyObject]? var callback:AckCallback? - init(event:String) { + init(event:String, ackNum:Int = 0) { + self.ackNum = ackNum self.event = event } diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 3f90746..c7740c2 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -25,6 +25,7 @@ import Foundation class SocketEvent { + let justAck:Bool! var ack:Int? var args:AnyObject! lazy var currentPlace = 0 @@ -32,11 +33,12 @@ class SocketEvent { var event:String! var placeholders:Int! - init(event:String, args:AnyObject?, placeholders:Int = 0, ack:Int? = nil) { + init(event:String, args:AnyObject?, placeholders:Int = 0, ackNum:Int? = nil, justAck:Bool = false) { self.event = event self.args = args self.placeholders = placeholders - self.ack = ack + self.ack = ackNum + self.justAck = justAck } func addData(data:NSData) -> Bool { @@ -64,22 +66,38 @@ class SocketEvent { } static 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)\"," + } } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index a8e044c..523e6dc 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -36,10 +36,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var ackHandlers = [SocketAckHandler]() - private var secure = false + 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 @@ -126,14 +127,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Sends a message with multiple args // If a message contains binary we have to send those // seperately. - func emit(event:String, _ args:AnyObject...) -> SocketAckHandler { + func emit(event:String, _ args:AnyObject...) { if !self.connected { - return SocketAckHandler(event: "fail") + return } - let ackHandler = SocketAckHandler(event: event) - self.ackHandlers.append(ackHandler) - dispatch_async(self.emitQueue) {[weak self] in if self == nil { return @@ -141,11 +139,29 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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]) { + private func _emit(event:String, _ args:[AnyObject], ack:Bool = false) { var frame:SocketEvent var str:String @@ -156,16 +172,27 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } if hasBinary { - str = SocketEvent.createMessageForEvent(event, withArgs: items, - hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp) + if !ack { + str = SocketEvent.createMessageForEvent(event, withArgs: items, + hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp) + } else { + str = SocketEvent.createMessageForEvent(event, withArgs: items, + hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) + } self.io?.send(str) for data in emitDatas { self.io?.send(data) } } else { - str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, - withDatas: 0, toNamespace: self.nsp) + if !ack { + str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, + withDatas: 0, toNamespace: self.nsp) + } else { + str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, + withDatas: 0, toNamespace: self.nsp, wantsAck: self.currentAck) + } + self.io?.send(str) } } @@ -207,6 +234,16 @@ 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 { + handler.callback?(data) + break + } + } + } + // Handles events func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { @@ -264,7 +301,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parse an NSArray looking for binary data - private class func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { + private static func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { var replacementArr = [AnyObject](count: arr.count, repeatedValue: 1) var hasBinary = false var arrayDatas = [NSData]() @@ -512,6 +549,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if ackNum == "" { self.handleEvent(event, data: parsed) } else { + self.currentAck = ackNum.toInt()! self.handleEvent(event, data: parsed, isInternalMessage: false, wantsAck: ackNum.toInt(), withAckType: 3) } @@ -525,6 +563,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if ackNum == "" { self.handleEvent(event, data: parsed) } else { + self.currentAck = ackNum.toInt()! self.handleEvent(event, data: parsed, isInternalMessage: false, wantsAck: ackNum.toInt(), withAckType: 3) } @@ -540,11 +579,31 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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]) + let 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 @@ -574,9 +633,13 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { /** Begin check for binary placeholders **/ - let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\"),(.*)\\]$"].groups() + let binaryGroup = mutMessage["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$"].groups() - if binaryGroup != nil { + if binaryGroup == nil { + return + } + + if binaryGroup[1].hasPrefix("45") { // println(binaryGroup) var ackNum:String var event:String @@ -611,11 +674,35 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { mes = SocketEvent(event: event, args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!) } else { + self.currentAck = ackNum.toInt()! mes = SocketEvent(event: event, args: placeholdersRemoved, - placeholders: numberOfPlaceholders.toInt()!, ack: ackNum.toInt()) + 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 + let ackNum:String + let 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 @@ -634,6 +721,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) + 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) @@ -643,6 +735,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() + 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) From 5d8479ed36a90da84fe32beda8f9c87c9768b457 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sun, 15 Feb 2015 13:22:19 -0500 Subject: [PATCH 11/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 834cf24..17cf2f4 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ socket.on("ackEvent") {data in println("Got ackEvent") } - socket.emit("ackTest", "test").onAck {data in + socket.emitWithAck("ackTest", "test").onAck {data in println(data) } From 44de79d8c58a60bd6e0aaa72d21e88e0ac515395 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 15 Feb 2015 18:51:43 -0500 Subject: [PATCH 12/42] dispatch events on main queue --- SwiftIO/SocketIOClient.swift | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 523e6dc..6065018 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -248,23 +248,25 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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 { - return - } - - 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) - } - } else { - handler.executeCallback(data) - if ack != nil { - self.emitAck(ack!, withEvent: event, - withData: handler.ack.ackData, withAckType: ackType) + dispatch_async(dispatch_get_main_queue()) { + if !self.connected && !isInternalMessage { + return + } + + 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) + } + } else { + handler.executeCallback(data) + if ack != nil { + self.emitAck(ack!, withEvent: event, + withData: handler.ack.ackData, withAckType: ackType) + } } } } From 170b1bc83627c7695f26becadad877af7b8a2fcd Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 23 Feb 2015 15:08:38 -0500 Subject: [PATCH 13/42] API CHANGE. See README --- README.md | 50 +++++++++++----------- SwiftIO/SocketAckHandler.swift | 5 --- SwiftIO/SocketEvent.swift | 27 +++++++++--- SwiftIO/SocketEventHandler.swift | 45 +++++++------------- SwiftIO/SocketIOClient.swift | 71 +++++++++++++++----------------- SwiftIO/SwiftRegex.swift | 28 ++++++------- 6 files changed, 110 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 17cf2f4..e227647 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.2 +Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.2. + +For Swift 1.1 use the master branch. Installation ============ @@ -13,11 +15,10 @@ 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. -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. @@ -43,9 +44,9 @@ 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 socket.emit("testEcho") @@ -58,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") } @@ -68,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 } @@ -114,16 +116,16 @@ socket.onMultipleItems("multipleItems") {data in println(obj["test"]) } } - + // 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 c7740c2..59f5639 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -65,7 +65,7 @@ class SocketEvent { } } - static func createMessageForEvent(event:String, withArgs args:[AnyObject], + class func createMessageForEvent(event:String, withArgs args:[AnyObject], hasBinary:Bool, withDatas datas:Int = 0, toNamespace nsp:String?, wantsAck ack:Int? = nil) -> String { var message:String @@ -104,36 +104,53 @@ class SocketEvent { return self.completeMessage(message, args: args) } - static 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) } } } - private static func completeMessage(var message:String, args:[AnyObject]) -> String { + private class func completeMessage(var message:String, args:[AnyObject]) -> String { var err:NSError? for arg in args { @@ -142,7 +159,7 @@ class SocketEvent { options: NSJSONWritingOptions(0), error: &err) let jsonString = NSString(data: jsonSend!, encoding: NSUTF8StringEncoding) - message += jsonString! as! String + message += jsonString! as String message += "," continue } 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 6065018..2aa1197 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), @@ -57,9 +54,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if mutURL["https://"].matches().count != 0 { self.secure = true - } else { - self.secure = false } + mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" @@ -198,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 @@ -209,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) } @@ -256,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) } } } @@ -280,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 @@ -303,7 +298,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parse an NSArray looking for binary data - private static func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { + private class func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { var replacementArr = [AnyObject](count: arr.count, repeatedValue: 1) var hasBinary = false var arrayDatas = [NSData]() @@ -352,7 +347,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses data for events - static func parseData(data:String?) -> AnyObject? { + class func parseData(data:String?) -> AnyObject? { if data == nil { return nil } @@ -370,7 +365,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return parsed } - private static func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { + private class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { var items = [AnyObject](count: args.count, repeatedValue: 1) var numberOfPlaceholders = -1 var hasBinary = false @@ -424,7 +419,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses a NSDictionary, looking for NSData objects - private static func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { + private class func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false if placeholders == -1 { @@ -589,7 +584,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } else if messageGroups[1].hasPrefix("43") { let arr = Array(messageGroups[1]) - let ackNum:String + var ackNum:String let nsp = messageGroups[2] if nsp == "" && self.nsp != nil { @@ -671,7 +666,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let mes:SocketEvent + var mes:SocketEvent if ackNum == "" { mes = SocketEvent(event: event, args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!) @@ -685,8 +680,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } else if binaryGroup[1].hasPrefix("46") { let messageType = RegexMutable(binaryGroup[1]) let numberOfPlaceholders = (messageType["46"] ~= "") as String - let ackNum:String - let nsp:String + var ackNum:String + var nsp:String if binaryGroup[3] == "" { ackNum = binaryGroup[2] diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 360d051..474f6c5 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -60,15 +60,15 @@ public class SwiftRegex: NSObject, BooleanType { } public func range(options: NSMatchingOptions = nil) -> NSRange { - return regex.rangeOfFirstMatchInString(target as! String, 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)) as! String + return substring(range(options: options)) as String } public func groups(options: NSMatchingOptions = nil) -> [String]! { - return groupsForMatch( regex.firstMatchInString(target as! String, options: options, range: targetRange) ) + return groupsForMatch( regex.firstMatchInString(target as String, options: options, range: targetRange) ) } func groupsForMatch(match: NSTextCheckingResult!) -> [String]! { @@ -95,7 +95,7 @@ public class SwiftRegex: NSObject, BooleanType { if let mutableTarget = target as? NSMutableString { for match in matchResults()!.reverse() { let replacement = regex.replacementStringForResult( match, - inString: target as! String, offset: 0, template: newValue ) + inString: target as String, offset: 0, template: newValue ) mutableTarget.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) } } else { @@ -105,7 +105,7 @@ public class SwiftRegex: NSObject, BooleanType { } func matchResults(options: NSMatchingOptions = nil) -> [NSTextCheckingResult]? { - let matches = regex.matchesInString(target as! String, options: options, range: targetRange) + let matches = regex.matchesInString(target as String, options: options, range: targetRange) as? [NSTextCheckingResult] if matches != nil { @@ -130,8 +130,8 @@ public class SwiftRegex: NSObject, BooleanType { public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { var out = Dictionary() for match in matchResults(options: options)! { - out[substring(match.rangeAtIndex(1)) as! String] = - substring(match.rangeAtIndex(2)) as? String + out[substring(match.rangeAtIndex(1)) as String] = + substring(match.rangeAtIndex(2)) as String } return out } @@ -141,16 +141,16 @@ public class SwiftRegex: NSObject, BooleanType { let out = NSMutableString() var pos = 0 - regex.enumerateMatchesInString(target as! String, 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) ) as! String ) + 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) ) as! String ) + out.appendString( substring( NSRange(location:pos, length:targetRange.length-pos) ) as String ) if let mutableTarget = target as? NSMutableString { mutableTarget.setString(out as String) @@ -215,13 +215,13 @@ extension String { } public func RegexMutable(string: NSString) -> NSMutableString { - return NSMutableString(string:string as! String) + return NSMutableString(string:string as String) } public func ~= (left: SwiftRegex, right: String) -> NSMutableString { return left.substituteMatches({match, stop in return left.regex.replacementStringForResult( match, - inString: left.target as! String, offset: 0, template: right ) + inString: left.target as String, offset: 0, template: right ) }, options: nil) } @@ -234,7 +234,7 @@ public func ~= (left: SwiftRegex, right: [String]) -> NSMutableString { } return left.regex.replacementStringForResult( match, - inString: left.target as! String, offset: 0, template: right[matchNumber-1] ) + inString: left.target as String, offset: 0, template: right[matchNumber-1] ) }, options: nil) } @@ -242,7 +242,7 @@ public func ~= (left: SwiftRegex, right: (String) -> String) -> NSMutableString // return right(left.substring(match.range)) return left.substituteMatches( {match, stop -> String in - right(left.substring(match.range) as! String) + right(left.substring(match.range) as String) }, options: nil) } From 306ac47168e944befb628a3a4c6484be583d0201 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Mon, 23 Feb 2015 15:14:12 -0500 Subject: [PATCH 14/42] Remove println --- SwiftIO/SocketEvent.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 59f5639..f18aea0 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 516d9b3eea3c615ec96244fdb2367f519272a2cf Mon Sep 17 00:00:00 2001 From: Erik Little Date: Mon, 23 Feb 2015 17:24:18 -0500 Subject: [PATCH 15/42] 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 f18aea0..c647308 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 5b9537065ff934a4532e73c29adba8fbeb9a4159 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 24 Feb 2015 12:59:56 -0500 Subject: [PATCH 16/42] #15 add connectWithParams --- README.md | 1 + SwiftIO/SocketIOClient.swift | 55 +++++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e227647..1bf447c 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ 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.connectWithParams` - Establishes a connection to the server, passing the specified params. 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 diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 2aa1197..214c30e 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 50ac79e07e995c8a5bc872dfaeb944db7f7ba309 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 13:03:34 -0500 Subject: [PATCH 17/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bf447c..a242d39 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ 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.connectWithParams` - Establishes a connection to the server, passing the specified params. 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. 6. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events From ecd76d61060d3538f329c7ad1e2fb2aebf35af91 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 13:07:07 -0500 Subject: [PATCH 18/42] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a242d39..7189700 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Methods 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.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server, passing the specified params. A "connect" event is fired upon successful connection. -6. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. +7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. Events ------ From 3fbc396d8576f6009c7881726f796c2b314abc6a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 24 Feb 2015 13:25:03 -0500 Subject: [PATCH 19/42] send close event --- SwiftIO/SocketIOClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 214c30e..44815fd 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() } From 43ccf6dd455ab0801de9dfcac43847d63f8dc66f Mon Sep 17 00:00:00 2001 From: Erik Little Date: Tue, 24 Feb 2015 18:16:02 -0500 Subject: [PATCH 20/42] Remove ackhandlers --- SwiftIO/SocketIOClient.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 44815fd..a17e24a 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 } } } @@ -889,4 +891,4 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.tryReconnect(triesLeft: self.reconnectAttempts) } } -} \ No newline at end of file +} From e7973ba88f69ef12dd80327caa2eb2e2692a9d8a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 24 Feb 2015 18:33:17 -0500 Subject: [PATCH 21/42] I was being stupid again --- SwiftIO/SocketEvent.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index c647308..ac11726 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 { @@ -257,4 +249,4 @@ class SocketEvent { return false } -} +} \ No newline at end of file From 02b1314a3db839c2be2bd7a9d6aa388fdaa7568a Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 2 Mar 2015 13:52:01 -0500 Subject: [PATCH 22/42] fix emit with no args --- SwiftIO/SocketEvent.swift | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index ac11726..2bd873c 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 { @@ -157,6 +159,7 @@ class SocketEvent { if message != "" { message.removeAtIndex(message.endIndex.predecessor()) } + return message + "]" } From 1ac05d9790cc5322b71ffe11b8f8c0410fad8480 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 23:41:15 -0500 Subject: [PATCH 23/42] add new things to 1.2 --- README.md | 22 +- SwiftIO/SocketAckHandler.swift | 2 +- SwiftIO/SocketEngine.swift | 596 +++++++++++++++++++++++++++++++ SwiftIO/SocketEvent.swift | 24 +- SwiftIO/SocketEventHandler.swift | 6 +- SwiftIO/SocketIOClient.swift | 463 +++++++++++------------- 6 files changed, 837 insertions(+), 276 deletions(-) create mode 100644 SwiftIO/SocketEngine.swift diff --git a/README.md b/README.md index 7189700..48a9c36 100644 --- a/README.md +++ b/README.md @@ -1,9 +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.2. +Socket.IO-client for Swift. Supports ws/wss/polling connections and binary. For socket.io 1.0+ and Swift 1.2. -For Swift 1.1 use the master branch. +For Swift 1.1 use the 1.1 branch. Installation ============ @@ -15,14 +15,15 @@ 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. +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. +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 @@ -41,9 +42,13 @@ 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) ]) +// 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") @@ -67,7 +72,7 @@ socket.on("ackEvent") {data, ack in } socket.emitWithAck("ackTest", "test").onAck {data in - println(data) + println(data?[0]) } ack?("Got your event", "dude") @@ -98,8 +103,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 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 new file mode 100644 index 0000000..0338090 --- /dev/null +++ b/SwiftIO/SocketEngine.swift @@ -0,0 +1,596 @@ +// +// 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 + +// 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 count(self) + } +} + +private typealias PollWaitQueue = [() -> Void] + +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 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 + 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 pingInterval:Int? + var polling:Bool { + return self._polling + } + var sid = "" + var urlPolling:String? + var urlWebSocket:String? + var websocket:Bool { + return self._websocket + } + var ws:SRWebSocket? + + 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() { + self.pingTimer?.invalidate() + self.send(PacketType.CLOSE.rawValue) + } + + 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 + var urlWebSocket:String + + if self.client.secure { + urlPolling = "https://" + url + "polling" + urlWebSocket = "wss://" + url + "websocket" + } else { + urlPolling = "http://" + url + "polling" + urlWebSocket = "ws://" + url + "websocket" + } + + 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 (urlPolling, urlWebSocket) + } + + private func doPoll() { + if self.urlPolling == nil || self.websocket || self.waitingForPoll || !self.connected { + return + } + + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + 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) + } + 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() + } + + private func flushProbeWait() { + // 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 { + return + } else if self.websocket { + self.flushWaitingForPostToWebSocket() + return + } + + var postStr = "" + + for packet in self.postWait { + let len = count(packet) + + postStr += "\(len):\(packet)" + } + + self.postWait.removeAll(keepCapacity: false) + + let 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 + + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in + if self == nil { + return + } else if err != nil && self!.polling { + self?.handlePollingFailed(err) + return + } + + self?.waitingForPost = false + dispatch_async(self!.emitQueue) { + self?.flushWaitingForPost() + self?.doPoll() + return + } + }.resume() + } + + // 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?) { + if !self.client.reconnecting { + self.connected = false + self.ws?.close() + 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 reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) + + self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in + 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 + } + + 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: &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 + private func parsePollingMessage(str:String) { + if str.length == 1 { + return + } + + // println(str) + + let strArray = Array(str) + var length = "" + var n = 0 + var msg = "" + + func testLength(length:String, inout n:Int) -> Bool { + if let num = length.toInt() { + n = num + } else { + return true + } + + return false + } + + for var i = 0, l = str.length; i < l; i = i &+ 1 { + let chr = String(strArray[i]) + + if chr != ":" { + length += chr + } else { + if length == "" || testLength(length, &n) { + self.handlePollingFailed(nil) + 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 { + // 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 + length = "" + } + } + } + + 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) { + // println("sending \(data)") + self.client.parseSocketMessage(data) + } + + return + } else if type == PacketType.NOOP.rawValue { + self.doPoll() + return + } + + 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() { + if self.websocketConnected { + self.sendWebSocketMessage("probe", withType: PacketType.PING) + } + } + + func send(msg:String, datas:[NSData]? = nil) { + let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in + return { + if self == nil || !self!.connected { + return + } + + if self!.websocket { + // println("sending ws: \(msg):\(datas)") + self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) + } else { + // println("sending poll: \(msg):\(datas)") + self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: 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)() + } + } + } + + func sendPing() { + if self.websocket { + self.sendWebSocketMessage("", withType: PacketType.PING) + } else { + self.sendPollMessage("", withType: PacketType.PING) + } + } + + 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!) + } + } + + 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)") + self.ws?.send("\(type.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 { + 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) + } + } + + private func upgradeTransport() { + if self.websocketConnected { + self.probing = false + self._websocket = true + self.waitingForPoll = false + self._polling = false + self.sendWebSocketMessage("", withType: PacketType.UPGRADE) + self.flushProbeWait() + } + } + + // 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!) { + 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 + + 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() + } + } + + // 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() + } + } +} \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 2bd873c..b0c6aad 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)[" } } diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 3fe9204..c59e92c 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 { @@ -44,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 a17e24a..1e60258 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient: NSObject, SRWebSocketDelegate { +class SocketIOClient { let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -32,30 +32,36 @@ 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]() + 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 private var handlers = [SocketEventHandler]() - private var lastSocketMessage:SocketEvent? + private var waitingData = [SocketEvent]() private var paramConnect = false - private var pingTimer:NSTimer! - private var secure = false + private var _secure = false + private var reconnectTimer:NSTimer? var closed = false var connected = false var connecting = false - var io:SRWebSocket? + var engine:SocketEngine? var nsp:String? var reconnects = true 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://"] ~= "" @@ -71,6 +77,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let reconnectAttempts = opts!["reconnectAttempts"] as? Int { self.reconnectAttempts = reconnectAttempts + } else { + self.reconnectAttempts = -1 } if let reconnectWait = opts!["reconnectWait"] as? Int { @@ -80,75 +88,68 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let nsp = opts!["nsp"] as? String { self.nsp = nsp } + + if let polling = opts!["forcePolling"] as? Bool { + self.forcePolling = polling + } + } else { + self.reconnectAttempts = -1 } + + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } // Closes the socket func close() { - self.pingTimer?.invalidate() self.closed = true self.connecting = false self.connected = false - self.io?.send("41") - self.io?.close() + self.reconnecting = false + self.engine?.close() } // Connects to the server func connect() { - self.connectWithURL(self.createConnectURL()) + 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 - 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) + self.engine?.open(opts: params) } - private func connectWithURL(url:String) { - if self.closed { - println("Warning: This socket was previvously closed. Reopening could be dangerous. Be careful.") - } - - self.connecting = true + func didConnect() { self.closed = false - - self.io = SRWebSocket(URL: NSURL(string: url)) - self.io?.delegate = self - self.io?.open() + 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) } - // 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 func createConnectURL() -> String { - if self.secure { - return "wss://\(self.socketURL)/socket.io/?transport=websocket" - } else { - return "ws://\(self.socketURL)/socket.io/?transport=websocket" - } + // Server wants us to die + func didForceClose() { + self.closed = true + 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 @@ -160,11 +161,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args) + return } } @@ -178,11 +176,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.ackHandlers.append(ackHandler) dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args, ack: true) + return } return ackHandler @@ -207,10 +202,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) - for data in emitDatas { - self.io?.send(data) - } + self.engine?.send(str, datas: emitDatas) } else { if !ack { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, @@ -220,7 +212,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withDatas: 0, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) + self.engine?.send(str) } } @@ -243,7 +235,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, @@ -253,10 +245,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - self?.io?.send(str) - for data in emitDatas { - self?.io?.send(data) - } + self?.engine?.send(str, datas: emitDatas) } } } @@ -267,7 +256,14 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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 } } @@ -277,45 +273,48 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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 data is NSArray { - if ack != nil { - handler.executeCallback(data as? NSArray, withAck: ack!, - withAckType: ackType, withSocket: self) - } else { - handler.executeCallback(data as? NSArray) - } + 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 { + 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) } } } } } - private func joinNamespace() { + // 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!)") } } @@ -325,35 +324,38 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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() } // 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]() @@ -408,9 +411,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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 @@ -421,11 +424,11 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } 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) @@ -438,11 +441,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } else if let binaryData = args[i] as? NSData { // args is just binary hasBinary = true - let sendData = SocketIOClient.createBinaryDataForSend(binaryData) - numberOfPlaceholders++ - items[i] = ["_placeholder": true, "num": numberOfPlaceholders] - emitDatas.append(sendData) + currentPlaceholder++ + items[i] = ["_placeholder": true, "num": currentPlaceholder] + emitDatas.append(binaryData) } else { items[i] = args[i] } @@ -452,39 +454,36 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // 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 - let sendData = self.createBinaryDataForSend(binaryData) - returnDatas.append(sendData) - returnDict[key as! String] = ["_placeholder": true, "num": placeholders++] + returnDatas.append(binaryData) + 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 @@ -498,7 +497,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses messages recieved - private func parseSocketMessage(message:AnyObject?) { + func parseSocketMessage(message:AnyObject?) { if message == nil { return } @@ -508,40 +507,33 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let stringMessage = message as? String { // Check for successful namepsace connect if self.nsp != nil { - if stringMessage == "40/\(self.nsp!)" { - self.handleEvent("connect", data: nil) + if stringMessage == "0/\(self.nsp!)" { + self.didConnect() 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 + 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 } } - /** - 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? @@ -550,7 +542,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 } @@ -615,7 +607,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] @@ -625,7 +617,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] } @@ -645,7 +637,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // Message is binary if let binary = message as? NSData { - if self.lastSocketMessage == nil { + if self.waitingData.isEmpty { return } @@ -669,7 +661,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - if binaryGroup[1].hasPrefix("45") { + if binaryGroup[1].hasPrefix("5") { // println(binaryGroup) var ackNum:String var event:String @@ -687,12 +679,11 @@ 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]) if namespace == "" && self.nsp != nil { - self.lastSocketMessage = nil return } @@ -709,10 +700,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) } - self.lastSocketMessage = mes - } else if binaryGroup[1].hasPrefix("46") { + self.waitingData.append(mes) + } 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 @@ -731,8 +722,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { 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 @@ -742,37 +735,42 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { // 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! { - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + if socketEvent.justAck! { + // Should handle ack + self.handleAck(socketEvent.ack!, data: filledInArgs) return } - if self.lastSocketMessage!.ack != nil { + // Should do event + 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() - if self.lastSocketMessage!.justAck! { - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + // Should handle ack + if socketEvent.justAck! { + self.handleAck(socketEvent.ack!, data: filledInArgs) return } - if self.lastSocketMessage!.ack != nil { + // Should handle ack + 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) } @@ -780,27 +778,19 @@ 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) + // Something happened while polling + func pollingDidFail(err:NSError?) { + if !self.reconnecting { + self.connected = false + self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) + self.tryReconnect() } } // We lost connection and should attempt to reestablish - private func tryReconnect(var #triesLeft:Int) { - if triesLeft != -1 && triesLeft <= 0 { - self.connecting = false - self.reconnects = false - self.reconnecting = false - self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) + @objc func tryReconnect() { + if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { + self.didForceClose() return } else if self.connected { self.connecting = false @@ -808,26 +798,25 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - // println("Trying to reconnect #\(reconnectAttempts - 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)) - - // 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 { @@ -835,60 +824,28 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - // 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 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) { - self.pingTimer?.invalidate() + 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) - + self.tryReconnect() } } // Called when an error occurs. - func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.pingTimer?.invalidate() + func webSocketDidFailWithError(error:NSError!) { self.connected = false 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) + self.tryReconnect() } } -} +} \ No newline at end of file From 94d715aaf9fe51d3a8b3825e1666b665b4e80ee3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 7 Mar 2015 19:48:41 -0500 Subject: [PATCH 24/42] add link to example project --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 48a9c36..69280e1 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 f14d9ef5fffd7360c251a61630c5baee64c7e387 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 11 Mar 2015 18:08:05 -0400 Subject: [PATCH 25/42] add new stuff to 1.2 --- README.md | 70 +-- SwiftIO/SocketAckHandler.swift | 11 +- SwiftIO/SocketEngine.swift | 103 ++--- SwiftIO/SocketEventHandler.swift | 8 +- SwiftIO/SocketIOClient.swift | 268 ++++++------ SwiftIO/WebSocket.swift | 713 +++++++++++++++++++++++++++++++ 6 files changed, 923 insertions(+), 250 deletions(-) create mode 100644 SwiftIO/WebSocket.swift diff --git a/README.md b/README.md index 69280e1..63b7696 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,11 @@ Socket.IO-Client-Swift Socket.IO-client for Swift. Supports ws/wss/polling connections and binary. For socket.io 1.0+ and Swift 1.2. -For Swift 1.1 use the 1.1 branch. +For Swift 1.1 use the master 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 === @@ -21,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 ------ @@ -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,30 +103,23 @@ 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() +``` + +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"); + }]; +}]; -// Sending binary -socket.emit("testData", [ - "data": "Hello World".dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)!, - "test": true]) ``` Detailed Example diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index 17a4236..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? @@ -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 0338090..e138d4d 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 @@ -147,7 +147,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { 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 @@ -166,6 +167,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(str) dispatch_async(self!.parseQueue) {[weak self] in + if self == nil { + return + } + self?.parsePollingMessage(str) return } @@ -236,15 +241,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.doPoll() return - } - }.resume() + }}.resume() } // 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.ws?.writeString(msg) } self.postWait.removeAll(keepCapacity: true) @@ -255,7 +259,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 @@ -309,10 +313,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") @@ -326,8 +330,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.doPoll() self?.startPingTimer() - } - }.resume() + }}.resume() } // Translatation of engine.io-parser#decodePayload @@ -377,8 +380,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 +392,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 +411,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 +429,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - if messageString == PacketType.CLOSE.rawValue { + if message == PacketType.CLOSE.rawValue { // do nothing return } @@ -436,10 +438,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() { @@ -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..09f99b7 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -22,9 +22,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -typealias NormalCallback = (NSArray?, AckEmitter?) -> Void -typealias AnyHandler = (event:String, items:AnyObject?) -typealias AckEmitter = (AnyObject...) -> Void +import Foundation + +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 1e60258..ba279d8 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 @@ -257,11 +269,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 @@ -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,77 +509,86 @@ class SocketIOClient { } // Parses messages recieved - func parseSocketMessage(message:AnyObject?) { - if message == nil { - return - } - + internal 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 { @@ -576,78 +597,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) @@ -734,7 +730,7 @@ class SocketIOClient { } // Handles binary data - private func parseBinaryData(data:NSData) { + internal func parseBinaryData(data:NSData) { let shouldExecute = self.waitingData[0].addData(data) if shouldExecute { @@ -779,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) @@ -788,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 @@ -809,8 +805,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..ad1bf7e --- /dev/null +++ b/SwiftIO/WebSocket.swift @@ -0,0 +1,713 @@ +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Websocket.swift +// +// Created by Dalton Cherry on 7/16/14. +// +////////////////////////////////////////////////////////////////////////////////////////////////// + +import Foundation + +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 as String) + outputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + } + 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 + public 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(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\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 = offset + Int(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 = offset + Int(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! as String) + } + self.delegate?.websocketDidReceiveMessage(self, text: str! as String) + }) + } 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, Int(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 4551cad9e105becaed794b264cc13188d357414f Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 13 Mar 2015 10:08:41 -0400 Subject: [PATCH 26/42] update 1.2 --- SwiftIO/SocketEngine.swift | 218 ++++++------ SwiftIO/SocketEvent.swift | 2 +- SwiftIO/SocketIOClient.swift | 649 +++++++---------------------------- SwiftIO/SocketParser.swift | 451 ++++++++++++++++++++++++ SwiftIO/WebSocket.swift | 66 ++-- 5 files changed, 715 insertions(+), 671 deletions(-) create mode 100644 SwiftIO/SocketParser.swift diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 8864e4d..881127f 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -42,7 +42,7 @@ private enum PacketType: String { case NOOP = "6" } -class SocketEngine: NSObject, WebSocketDelegate { +public class SocketEngine: NSObject, WebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( @@ -78,19 +78,19 @@ class SocketEngine: NSObject, WebSocketDelegate { return self._websocket } var ws:WebSocket? - + 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() { self.pingTimer?.invalidate() self.send(PacketType.CLOSE.rawValue) } - + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { if self.websocket { var byteArray = [UInt8](count: 1, repeatedValue: 0x0) @@ -102,16 +102,16 @@ class SocketEngine: NSObject, WebSocketDelegate { 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 var urlWebSocket:String - + if self.client.secure { urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" @@ -119,14 +119,14 @@ class SocketEngine: NSObject, WebSocketDelegate { urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } - + 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())! @@ -138,10 +138,10 @@ class SocketEngine: NSObject, WebSocketDelegate { } } } - + return (urlPolling, urlWebSocket) } - + private func doFastUpgrade() { self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self._websocket = true @@ -149,23 +149,23 @@ class SocketEngine: NSObject, WebSocketDelegate { self.fastUpgrade = false self.flushProbeWait() } - + private func doPoll() { if self.websocket || self.waitingForPoll || !self.connected { return } - + 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 { return @@ -173,20 +173,18 @@ 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) {callback(str)} } - + self?.waitingForPoll = false - + if self!.fastUpgrade { self?.doFastUpgrade() return @@ -195,22 +193,22 @@ class SocketEngine: NSObject, WebSocketDelegate { } }.resume() } - + private func flushProbeWait() { // 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 { return @@ -218,31 +216,30 @@ class SocketEngine: NSObject, WebSocketDelegate { self.flushWaitingForPostToWebSocket() return } - + var postStr = "" - + for packet in self.postWait { let len = count(packet) - + postStr += "\(len):\(packet)" } - + self.postWait.removeAll(keepCapacity: false) - + let 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 - self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return @@ -250,7 +247,7 @@ class SocketEngine: NSObject, WebSocketDelegate { self?.handlePollingFailed(err) return } - + self?.waitingForPost = false dispatch_async(self!.emitQueue) { self?.flushWaitingForPost() @@ -258,21 +255,21 @@ class SocketEngine: NSObject, WebSocketDelegate { return }}.resume() } - + // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { for msg in self.postWait { self.ws?.writeString(msg) } - + self.postWait.removeAll(keepCapacity: true) } - + // A poll failed, tell the client about it private func handlePollingFailed(reason:NSError?) { assert(self.polling, "Polling failed when we're not polling") - + if !self.client.reconnecting { self._connected = false self.ws?.disconnect() @@ -282,18 +279,17 @@ class SocketEngine: NSObject, WebSocketDelegate { self.client.pollingDidFail(reason) } } - + func open(opts:[String: AnyObject]? = nil) { if 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 reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) - + self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in var err2:NSError? if self == nil { @@ -302,31 +298,31 @@ class SocketEngine: NSObject, WebSocketDelegate { 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 } - + 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: &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)")!) self?.ws?.queue = self?.handleQueue @@ -337,43 +333,43 @@ class SocketEngine: NSObject, WebSocketDelegate { 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 private func parsePollingMessage(str:String) { if str.length == 1 { return } - + // println(str) - + let strArray = Array(str) var length = "" var n = 0 var msg = "" - + func testLength(length:String, inout n:Int) -> Bool { if let num = length.toInt() { n = num } else { return true } - + return false } - + for var i = 0, l = str.length; i < l; i = i &+ 1 { let chr = String(strArray[i]) - + if chr != ":" { length += chr } else { @@ -381,16 +377,16 @@ class SocketEngine: NSObject, WebSocketDelegate { println("failure in parsePollingMessage") 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 { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in @@ -398,50 +394,50 @@ class SocketEngine: NSObject, WebSocketDelegate { return } } - + i += n length = "" } } } - + private func parseEngineData(data:NSData) { self.client.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) } - + private func parseEngineMessage(var message:String) { // println(message!) - + var strMessage = RegexMutable(message) - + // 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 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) { // println("sending \(data)") self.client.parseBinaryData(data) } - + return } else if type == PacketType.NOOP.rawValue { self.doPoll() return } - + if message == PacketType.CLOSE.rawValue { // do nothing return @@ -449,42 +445,42 @@ class SocketEngine: NSObject, WebSocketDelegate { // println("Got something idk what to do with") // println(messageString) } - + // Remove message type message.removeAtIndex(message.startIndex) // println("sending \(messageString)") - + self.client.parseSocketMessage(message) } - + private func probeWebSocket() { if self.websocketConnected { self.sendWebSocketMessage("probe", withType: PacketType.PING) } } - - func send(msg:String, datas:[NSData]? = nil) { + + public func send(msg:String, datas:[NSData]? = nil) { let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { if self == nil || !self!.connected { return } - + if self!.websocket { // println("sending ws: \(msg):\(datas)") self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { // println("sending poll: \(msg):\(datas)") - self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas, doPoll: true) + self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) } } } - + dispatch_async(self.emitQueue) {[weak self] in if self == nil { return } - + if self!.probing { self?.probeWait.append(_send(msg, datas)) } else { @@ -492,41 +488,37 @@ class SocketEngine: NSObject, WebSocketDelegate { } } } - + func sendPing() { if self.websocket { self.sendWebSocketMessage("", withType: PacketType.PING) } else { - self.sendPollMessage("", withType: PacketType.PING, doPoll: false) + self.sendPollMessage("", withType: PacketType.PING) } } - + private func sendPollMessage(msg:String, withType type:PacketType, - datas:[NSData]? = nil, doPoll poll:Bool) { + 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!) } } - - if !self.waitingForPoll && self.waitingForPost && poll { - self.doPoll() - } else { - self.flushWaitingForPost() - } + + self.flushWaitingForPost() } - + private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending ws: \(str) as type: \(type.rawValue)") self.ws?.writeString("\(type.rawValue)\(str)") - + if datas != nil { for data in datas! { let (data, nilString) = self.createBinaryDataForSend(data) @@ -536,39 +528,39 @@ class SocketEngine: NSObject, WebSocketDelegate { } } } - + // 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) } } - + private func upgradeTransport() { if self.websocketConnected { // Do a fast upgrade self.fastUpgrade = true self.probing = false - self.sendPollMessage("", withType: PacketType.NOOP, doPoll: false) + self.sendPollMessage("", withType: PacketType.NOOP) } } - - func websocketDidConnect(socket:WebSocket) { + + public func websocketDidConnect(socket:WebSocket) { self.websocketConnected = true self.probing = true self.probeWebSocket() } - - func websocketDidDisconnect(socket:WebSocket, error:NSError?) { + + public func websocketDidDisconnect(socket:WebSocket, error:NSError?) { self.websocketConnected = false self.probing = false - + if self.websocket { self.pingTimer?.invalidate() self._connected = false @@ -579,12 +571,12 @@ class SocketEngine: NSObject, WebSocketDelegate { self.flushProbeWait() } } - - func websocketDidReceiveMessage(socket:WebSocket, text:String) { + + public func websocketDidReceiveMessage(socket:WebSocket, text:String) { self.parseEngineMessage(text) } - - func websocketDidReceiveData(socket:WebSocket, data:NSData) { + + public func websocketDidReceiveData(socket:WebSocket, data:NSData) { self.parseEngineData(data) } -} +} \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index b0c6aad..fd8ef32 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -226,7 +226,7 @@ class SocketEvent { // We have multiple items // Do it live let argsAsArray = "[\(self.args)]" - if let parsedArr = SocketIOClient.parseData(argsAsArray) as? NSArray { + if let parsedArr = SocketParser.parseData(argsAsArray) as? NSArray { var returnArr = [AnyObject](count: parsedArr.count, repeatedValue: 0) for i in 0.. Void)? - private var currentAck = -1 + private var _closed = false + private var _connected = false + private var _connecting = false 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 _sid:String? + private var _reconnecting = false private var reconnectTimer:NSTimer? - var closed = false - var connected = false - var connecting = false - var engine:SocketEngine? - var nsp:String? - var reconnects = true - var reconnecting = false - var reconnectWait = 10 - var secure:Bool { + + internal var currentAck = -1 + internal var waitingData = [SocketEvent]() + + public var closed:Bool { + return self._closed + } + public var connected:Bool { + return self._connected + } + public var connecting:Bool { + return self._connecting + } + public var engine:SocketEngine? + public var nsp:String? + public var reconnects = true + public var reconnecting:Bool { + return self._reconnecting + } + public var reconnectWait = 10 + public var secure:Bool { return self._secure } - var sid:String? - + public var sid:String? { + return self._sid + } + public init(socketURL:String, opts:[String: AnyObject]? = nil) { var mutURL = RegexMutable(socketURL) - + if mutURL["https://"].matches().count != 0 { self._secure = true } - + mutURL = mutURL["http://"] ~= "" mutURL = mutURL["https://"] ~= "" - + self.socketURL = mutURL - + // Set options if opts != nil { if let reconnects = opts!["reconnects"] as? Bool { self.reconnects = reconnects } - + if let reconnectAttempts = opts!["reconnectAttempts"] as? Int { self.reconnectAttempts = reconnectAttempts } else { self.reconnectAttempts = -1 } - + if let reconnectWait = opts!["reconnectWait"] as? Int { self.reconnectWait = abs(reconnectWait) } - + if let nsp = opts!["nsp"] as? String { self.nsp = nsp } - + if let polling = opts!["forcePolling"] as? Bool { self.forcePolling = polling } } else { self.reconnectAttempts = -1 } - + super.init() - + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } - + // Closes the socket public func close() { - self.closed = true - self.connecting = false - self.connected = false - self.reconnecting = false + self._closed = true + self._connecting = false + self._connected = false + self._reconnecting = false self.engine?.close() } - + // Connects to the server public func connect() { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") - self.closed = false + self._closed = false } - + self.engine?.open() } - + // Connect to the server using params public func connectWithParams(params:[String: AnyObject]) { if self.closed { println("Warning! This socket was previously closed. This might be dangerous!") - self.closed = false + self._closed = false } - + self.params = params self.paramConnect = true - + self.engine?.open(opts: params) } - + func didConnect() { - self.closed = false - self.connected = true - self.connecting = false - self.reconnecting = false + self._closed = false + self._connected = true + self._connecting = false + self._reconnecting = false self.currentReconnectAttempt = 0 self.reconnectTimer?.invalidate() self.reconnectTimer = nil - self.sid = self.engine?.sid - + self._sid = self.engine?.sid + + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled self.handleEvent("connect", data: nil, isInternalMessage: false) } - + // Server wants us to die func didForceClose() { - self.closed = true - self.connected = false + self._closed = true + self._connected = false self.reconnects = false - self.connecting = false - self.reconnecting = false + self._connecting = false + self._reconnecting = false self.handleEvent("disconnect", data: "closed", isInternalMessage: true) } - + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -163,49 +182,49 @@ public class SocketIOClient: NSObject { if !self.connected { return } - + dispatch_async(self.emitQueue) {[weak self] in self?._emit(event, args) return } } - + // 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") } - + self.currentAck++ let ackHandler = SocketAckHandler(event: event, ackNum: self.currentAck) self.ackHandlers.append(ackHandler) - + dispatch_async(self.emitQueue) {[weak self] in self?._emit(event, args, ack: true) return } - + 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 - - let (items, hasBinary, emitDatas) = SocketIOClient.parseEmitArgs(args) - + + let (items, hasBinary, emitDatas) = SocketParser.parseEmitArgs(args) + if !self.connected { return } - + if hasBinary { if !ack { str = SocketEvent.createMessageForEvent(event, withArgs: items, @@ -214,7 +233,7 @@ public class SocketIOClient: NSObject { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - + self.engine?.send(str, datas: emitDatas) } else { if !ack { @@ -224,21 +243,21 @@ public class SocketIOClient: NSObject { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, withDatas: 0, toNamespace: self.nsp, wantsAck: self.currentAck) } - + self.engine?.send(str) } } - + // If the server wants to know that the client received data - internal func emitAck(ack:Int, 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 } - - let (items, hasBinary, emitDatas) = SocketIOClient.parseEmitArgs(data!) + + let (items, hasBinary, emitDatas) = SocketParser.parseEmitArgs(data!) var str:String - + if !hasBinary { if self?.nsp == nil { str = SocketEvent.createAck(ack, withArgs: items, @@ -247,7 +266,7 @@ public class SocketIOClient: NSObject { str = SocketEvent.createAck(ack, withArgs: items, withAckType: 3, withNsp: self!.nsp!) } - + self?.engine?.send(str) } else { if self?.nsp == nil { @@ -257,14 +276,14 @@ public class SocketIOClient: NSObject { str = SocketEvent.createAck(ack, withArgs: items, withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - + self?.engine?.send(str, datas: emitDatas) } } } - + // Called when the socket gets an ack for something it sent - private func handleAck(ack:Int, data:AnyObject?) { + func handleAck(ack:Int, data:AnyObject?) { self.ackHandlers = self.ackHandlers.filter {handler in if handler.ackNum != ack { return true @@ -276,12 +295,12 @@ public class SocketIOClient: NSObject { } else { handler.executeAck(nil) } - + return false } } } - + // Handles events public func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { @@ -289,7 +308,7 @@ public class SocketIOClient: NSObject { if !self.connected && !isInternalMessage { return } - + dispatch_async(dispatch_get_main_queue()) {[weak self] in self?.anyHandler?((event, data)) return @@ -304,15 +323,15 @@ public class SocketIOClient: NSObject { 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) @@ -323,492 +342,74 @@ public class SocketIOClient: NSObject { } } } - + // Should be removed and moved to SocketEngine func joinNamespace() { if self.nsp != nil { self.engine?.send("0/\(self.nsp!)") } } - + // Adds handler for an event public func on(name:String, callback:NormalCallback) { let handler = SocketEventHandler(event: name, callback: callback) self.handlers.append(handler) } - + // Adds a handler for any event public func onAny(handler:(AnyHandler) -> Void) { self.anyHandler = handler } - + // Opens the connection to the socket public func open() { self.connect() } - - // Parse an NSArray looking for binary data - 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]() - - for g in 0.. AnyObject? { - if data == nil { - return nil - } - - var err:NSError? - let stringData = data!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - let parsed:AnyObject? = NSJSONSerialization.JSONObjectWithData(stringData!, - options: NSJSONReadingOptions.AllowFragments, error: &err) - - if err != nil { - // println(err) - return nil - } - - return parsed + + func parseBinaryData(data:NSData) { + SocketParser.parseBinaryData(data, socket: self) } - - private class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { - var items = [AnyObject](count: args.count, repeatedValue: 1) - var currentPlaceholder = -1 - var hasBinary = false - var emitDatas = [NSData]() - - for i in 0.. (NSDictionary, Bool, [NSData]) { - var returnDict = NSMutableDictionary() - var hasBinary = false - 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": currentPlaceholder++] - } else if let arr = value as? NSArray { - let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) - - if hadBinary { - hasBinary = true - returnDict[key as! String] = replace - 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, currentPlaceholder: currentPlaceholder) - - if hadBinary { - hasBinary = true - returnDict[key as! String] = nestDict - currentPlaceholder += nestDatas.count - returnDatas.extend(nestDatas) - } else { - returnDict[key as! String] = dict - } - } else { - returnDict[key as! String] = value - } - } - - return (returnDict, hasBinary, returnDatas) - } - - // Parses messages recieved - internal func parseSocketMessage(stringMessage:String) { - // println(message!) - - // Check for successful namepsace connect - if self.nsp != nil { - if stringMessage == "0/\(self.nsp!)" { - self.didConnect() - return - } - } - - 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 - } - } - - 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 - } - - 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 - } else { - data = messageInternals[2] - } - - // 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 { - 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 - // 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]) - } else { - ackNum = messageGroups[3] - } - - let ackData:AnyObject? = SocketIOClient.parseData(messageGroups[4]) - self.handleAck(ackNum.toInt()!, data: ackData) - - return - } - /** - End Check for message - **/ - - // Check for message with binary placeholders - self.parseBinaryMessage(message: stringMessage) - } - - // Tries to parse a message that contains binary - private func parseBinaryMessage(#message:String) { - // println(message) - 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]) - - 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) - } - /** - End check for binary placeholders - **/ - } - - // Handles binary data - internal func parseBinaryData(data:NSData) { - let shouldExecute = self.waitingData[0].addData(data) - - 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 = socketEvent.fillInPlaceholders(args) - - if socketEvent.justAck! { - // Should handle ack - self.handleAck(socketEvent.ack!, data: filledInArgs) - return - } - - // Should do event - if socketEvent.ack != nil { - self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: socketEvent.ack!, withAckType: 6) - } else { - self.handleEvent(event, data: filledInArgs) - } - } else { - let filledInArgs:AnyObject = socketEvent.fillInPlaceholders() - - // Should handle ack - if socketEvent.justAck! { - self.handleAck(socketEvent.ack!, data: filledInArgs) - return - } - - // Should handle ack - if socketEvent.ack != nil { - self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: socketEvent.ack!, withAckType: 6) - } else { - self.handleEvent(event, data: filledInArgs) - } - } - } - } - + // Something happened while polling - internal func pollingDidFail(err:NSError?) { + func pollingDidFail(err:NSError?) { if !self.reconnecting { - self.connected = false + self._connected = false self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) self.tryReconnect() } } - + // We lost connection and should attempt to reestablish - internal func tryReconnect() { + func tryReconnect() { if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { self.didForceClose() return } else if self.connected { - self.connecting = false - self.reconnecting = false + self._connecting = false + self._reconnecting = false return } - + if self.reconnectTimer == nil { - self.reconnecting = true + 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 } } - + self.handleEvent("reconnectAttempt", data: self.reconnectAttempts - self.currentReconnectAttempt, isInternalMessage: true) - + self.currentReconnectAttempt++ if self.paramConnect { self.connectWithParams(self.params) @@ -816,11 +417,11 @@ public class SocketIOClient: NSObject { self.connect() } } - + // Called when the socket is closed func webSocketDidCloseWithCode(code:Int, reason:String!, wasClean:Bool) { - self.connected = false - self.connecting = false + self._connected = false + self._connecting = false if self.closed || !self.reconnects { self.didForceClose() } else { @@ -828,11 +429,11 @@ public class SocketIOClient: NSObject { self.tryReconnect() } } - + // Called when an error occurs. func webSocketDidFailWithError(error:NSError!) { - self.connected = false - self.connecting = false + self._connected = false + self._connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { self.didForceClose() @@ -841,4 +442,4 @@ public class SocketIOClient: NSObject { self.tryReconnect() } } -} +} \ No newline at end of file diff --git a/SwiftIO/SocketParser.swift b/SwiftIO/SocketParser.swift new file mode 100644 index 0000000..c41cea7 --- /dev/null +++ b/SwiftIO/SocketParser.swift @@ -0,0 +1,451 @@ +// +// SocketParser.swift +// Socket.IO-Swift +// +// 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 + +class SocketParser { + // Parse an NSArray looking for binary data + 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]() + + for g in 0.. AnyObject? { + if data == nil { + return nil + } + + var err:NSError? + let stringData = data!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + let parsed:AnyObject? = NSJSONSerialization.JSONObjectWithData(stringData!, + options: NSJSONReadingOptions.AllowFragments, error: &err) + + if err != nil { + // println(err) + return nil + } + + return parsed + } + + class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { + var items = [AnyObject](count: args.count, repeatedValue: 1) + var currentPlaceholder = -1 + var hasBinary = false + var emitDatas = [NSData]() + + for i in 0.. (NSDictionary, Bool, [NSData]) { + var returnDict = NSMutableDictionary() + var hasBinary = false + 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": currentPlaceholder] + } else if let arr = value as? NSArray { + let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) + + if hadBinary { + hasBinary = true + returnDict[key as! String] = replace + 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, currentPlaceholder: currentPlaceholder) + + if hadBinary { + hasBinary = true + returnDict[key as! String] = nestDict + currentPlaceholder += nestDatas.count + returnDatas.extend(nestDatas) + } else { + returnDict[key as! String] = dict + } + } else { + returnDict[key as! String] = value + } + } + + return (returnDict, hasBinary, returnDatas) + } + + // Parses messages recieved + class func parseSocketMessage(stringMessage:String, socket:SocketIOClient) { + // println(message!) + + // Check for successful namepsace connect + if socket.nsp != nil { + if stringMessage == "0/\(socket.nsp!)" { + socket.didConnect() + return + } + } + + if stringMessage == "0" { + if socket.nsp != nil { + // Join namespace + socket.joinNamespace() + return + } else { + socket.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 + } + + namespace = messageGroups[2] + messagePart = messageGroups[4] + + if namespace == "" && socket.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 + } else { + data = messageInternals[2] + } + + // It would be nice if socket.io only allowed one thing + // per message, but alas, it doesn't. + if let parsed:AnyObject = self.parseData(data) { + if ackNum == "" { + socket.handleEvent(event, data: parsed) + } else { + socket.currentAck = ackNum.toInt()! + socket.handleEvent(event, data: parsed, isInternalMessage: false, + 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 = self.parseData(asArray) { + if ackNum == "" { + socket.handleEvent(event, data: parsed) + } else { + socket.currentAck = ackNum.toInt()! + socket.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 == "" { + socket.handleEvent(event, data: nil) + } else { + socket.currentAck = ackNum.toInt()! + socket.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 == "" && socket.nsp != nil { + return + } + + if nsp == "" { + ackNum = String(arr[1...arr.count-1]) + } else { + ackNum = messageGroups[3] + } + + let ackData:AnyObject? = self.parseData(messageGroups[4]) + socket.handleAck(ackNum.toInt()!, data: ackData) + + return + } + /** + End Check for message + **/ + + // Check for message with binary placeholders + self.parseBinaryMessage(stringMessage, socket: socket) + } + + // Handles binary data + class func parseBinaryData(data:NSData, socket:SocketIOClient) { + let shouldExecute = socket.waitingData[0].addData(data) + + if shouldExecute { + let socketEvent = socket.waitingData.removeAtIndex(0) + var event = socketEvent.event + var parsedArgs:AnyObject? = self.parseData(socketEvent.args as? String) + + if let args:AnyObject = parsedArgs { + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders(args) + + if socketEvent.justAck! { + // Should handle ack + socket.handleAck(socketEvent.ack!, data: filledInArgs) + return + } + + // Should do event + if socketEvent.ack != nil { + socket.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: socketEvent.ack!, withAckType: 6) + } else { + socket.handleEvent(event, data: filledInArgs) + } + } else { + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders() + + // Should handle ack + if socketEvent.justAck! { + socket.handleAck(socketEvent.ack!, data: filledInArgs) + return + } + + // Should handle ack + if socketEvent.ack != nil { + socket.handleEvent(event, data: filledInArgs, isInternalMessage: false, + wantsAck: socketEvent.ack!, withAckType: 6) + } else { + socket.handleEvent(event, data: filledInArgs) + } + } + } + } + + // Tries to parse a message that contains binary + class func parseBinaryMessage(message:String, socket:SocketIOClient) { + // println(message) + 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]) + + namespace = binaryGroup[2] + if binaryGroup[3] != "" { + ackNum = binaryGroup[3] as String + } else if socket.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 == "" && socket.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 { + socket.currentAck = ackNum.toInt()! + mes = SocketEvent(event: event, args: placeholdersRemoved, + placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) + } + + socket.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 == "" && socket.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) + + socket.waitingData.append(event) + } + /** + End check for binary placeholders + **/ + } +} \ No newline at end of file diff --git a/SwiftIO/WebSocket.swift b/SwiftIO/WebSocket.swift index f868ae7..4643ce3 100644 --- a/SwiftIO/WebSocket.swift +++ b/SwiftIO/WebSocket.swift @@ -16,7 +16,7 @@ public protocol WebSocketDelegate: class { } public class WebSocket : NSObject, NSStreamDelegate { - + enum OpCode : UInt8 { case ContinueFrame = 0x0 case TextFrame = 0x1 @@ -27,7 +27,7 @@ public class WebSocket : NSObject, NSStreamDelegate { case Pong = 0xA //B-F reserved. } - + enum CloseCode : UInt16 { case Normal = 1000 case GoingAway = 1001 @@ -40,15 +40,15 @@ public class WebSocket : NSObject, NSStreamDelegate { 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" @@ -69,7 +69,7 @@ public class WebSocket : NSObject, NSStreamDelegate { let MaskMask: UInt8 = 0x80 let PayloadLenMask: UInt8 = 0x7F let MaxFrameSize: Int = 32 - + class WSResponse { var isFin = false var code: OpCode = .ContinueFrame @@ -77,7 +77,7 @@ public class WebSocket : NSObject, NSStreamDelegate { var frameCount = 0 var buffer: NSMutableData? } - + public weak var delegate: WebSocketDelegate? private var url: NSURL private var inputStream: NSInputStream? @@ -98,7 +98,7 @@ public class WebSocket : NSObject, NSStreamDelegate { public var isConnected :Bool { return connected } - + //init the websocket with a url public init(url: NSURL) { self.url = url @@ -130,43 +130,43 @@ public class WebSocket : NSObject, NSStreamDelegate { 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" { @@ -187,7 +187,7 @@ public class WebSocket : NSObject, NSStreamDelegate { for (key,value) in headers { self.addHeader(urlRequest, key: key, val: value) } - + let serializedRequest: NSData = CFHTTPMessageCopySerializedMessage(urlRequest.takeUnretainedValue()).takeUnretainedValue() self.initStreamsWithData(serializedRequest, Int(port!)) } @@ -215,14 +215,14 @@ public class WebSocket : NSObject, NSStreamDelegate { 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" { @@ -251,7 +251,7 @@ public class WebSocket : NSObject, NSStreamDelegate { } //delegate for the stream methods. Processes incoming bytes public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - + if eventCode == .HasBytesAvailable { if(aStream == inputStream) { processInputStream() @@ -282,7 +282,7 @@ public class WebSocket : NSObject, NSStreamDelegate { 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) @@ -365,7 +365,7 @@ public class WebSocket : NSObject, NSStreamDelegate { } 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) @@ -381,7 +381,7 @@ public class WebSocket : NSObject, NSStreamDelegate { } return false } - + ///process the websocket data private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { var response = readStack.last @@ -563,16 +563,16 @@ public class WebSocket : NSObject, NSStreamDelegate { } processResponse(response!) } - + let step = offset + Int(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 { @@ -581,7 +581,7 @@ public class WebSocket : NSObject, NSStreamDelegate { processRawMessage(buffer, bufferLen: bufferLen) } } - + ///process the finished response of a buffer private func processResponse(response: WSResponse) -> Bool { if response.isFin && response.bytesLeft <= 0 { @@ -615,14 +615,14 @@ public class WebSocket : NSObject, NSStreamDelegate { } 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)) @@ -674,7 +674,7 @@ public class WebSocket : NSObject, NSStreamDelegate { var maskKey = UnsafeMutablePointer(buffer + offset) SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) offset += sizeof(UInt32) - + for (var i = 0; i < dataLength; i++) { buffer[offset] = bytes[i] ^ maskKey[i % sizeof(UInt32)] offset += 1 @@ -706,8 +706,8 @@ public class WebSocket : NSObject, NSStreamDelegate { break } } - + } } - -} + +} \ No newline at end of file From c6d0331d50df58109a7d3aef7d1eadf454f42fe9 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 16 Mar 2015 09:06:45 -0400 Subject: [PATCH 27/42] merge timeouts fixes for 1.2 --- SwiftIO/SocketEngine.swift | 186 ++++++++++++++++++------------------- SwiftIO/SocketEvent.swift | 64 ++++++------- SwiftIO/SocketParser.swift | 124 ++++++++++++------------- SwiftIO/SwiftRegex.swift | 60 ++++++------ 4 files changed, 217 insertions(+), 217 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index d2116f0..20a54e7 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -78,19 +78,19 @@ public class SocketEngine: NSObject, WebSocketDelegate { return self._websocket } var ws:WebSocket? - + 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() { self.pingTimer?.invalidate() self.send(PacketType.CLOSE.rawValue) } - + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { if self.websocket { var byteArray = [UInt8](count: 1, repeatedValue: 0x0) @@ -102,16 +102,16 @@ public class SocketEngine: NSObject, WebSocketDelegate { 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 var urlWebSocket:String - + if self.client.secure { urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" @@ -119,14 +119,14 @@ public class SocketEngine: NSObject, WebSocketDelegate { urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } - + 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())! @@ -138,10 +138,10 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + return (urlPolling, urlWebSocket) } - + private func doFastUpgrade() { self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self._websocket = true @@ -149,23 +149,23 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.fastUpgrade = false self.flushProbeWait() } - + private func doPoll() { if self.websocket || self.waitingForPoll || !self.connected { return } - + 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 { return @@ -173,18 +173,18 @@ public class SocketEngine: NSObject, WebSocketDelegate { if self!.polling { self?.handlePollingFailed(err) } - + return } - + // println(data) - + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { - dispatch_async(self!.parseQueue) {callback(str)} + dispatch_async(self!.parseQueue) {callback(str as String)} } - + self?.waitingForPoll = false - + if self!.fastUpgrade { self?.doFastUpgrade() return @@ -193,22 +193,22 @@ public class SocketEngine: NSObject, WebSocketDelegate { } }.resume() } - + private func flushProbeWait() { // 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 { return @@ -216,29 +216,29 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.flushWaitingForPostToWebSocket() return } - + var postStr = "" - + for packet in self.postWait { let len = count(packet) - + postStr += "\(len):\(packet)" } - + self.postWait.removeAll(keepCapacity: false) - + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - + req.HTTPMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - + // NSLog("posting: \(postStr)") req.HTTPBody = postData req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - + self.waitingForPost = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { @@ -247,7 +247,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { self?.handlePollingFailed(err) return } - + self?.waitingForPost = false dispatch_async(self!.emitQueue) { self?.flushWaitingForPost() @@ -255,21 +255,21 @@ public class SocketEngine: NSObject, WebSocketDelegate { return }}.resume() } - + // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { for msg in self.postWait { self.ws?.writeString(msg) } - + self.postWait.removeAll(keepCapacity: true) } - + // A poll failed, tell the client about it private func handlePollingFailed(reason:NSError?) { assert(self.polling, "Polling failed when we're not polling") - + if !self.client.reconnecting { self._connected = false self.ws?.disconnect() @@ -279,17 +279,17 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.client.pollingDidFail(reason) } } - + func open(opts:[String: AnyObject]? = nil) { if 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 reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) - + self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in var err2:NSError? if self == nil { @@ -298,30 +298,30 @@ public class SocketEngine: NSObject, WebSocketDelegate { self?.handlePollingFailed(err) return } - + if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { let parsed:[String]? = dataString["(\\d*):(\\d)(\\{.*\\})?"].groups() - + if parsed == nil || 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: &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)")!) self?.ws?.queue = self?.handleQueue @@ -332,43 +332,43 @@ public class SocketEngine: NSObject, WebSocketDelegate { 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 private func parsePollingMessage(str:String) { if str.length == 1 { return } - + // println(str) - + let strArray = Array(str) var length = "" var n = 0 var msg = "" - + func testLength(length:String, inout n:Int) -> Bool { if let num = length.toInt() { n = num } else { return true } - + return false } - + for var i = 0, l = str.length; i < l; i = i &+ 1 { let chr = String(strArray[i]) - + if chr != ":" { length += chr } else { @@ -376,16 +376,16 @@ public class SocketEngine: NSObject, WebSocketDelegate { println("failure in parsePollingMessage") 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 { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in @@ -393,36 +393,36 @@ public class SocketEngine: NSObject, WebSocketDelegate { return } } - + i += n length = "" } } } - + private func parseEngineData(data:NSData) { self.client.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) } - + private func parseEngineMessage(var message:String) { // NSLog("Engine got message: \(message)") - + // We should upgrade if message == "3probe" { self.upgradeTransport() return } - + let type = message["^(\\d)"].groups()?[1] - + if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets 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) { // println("sending \(data)") @@ -431,13 +431,13 @@ public class SocketEngine: NSObject, WebSocketDelegate { return } } - + return } else if type == PacketType.NOOP.rawValue { self.doPoll() return } - + if message == PacketType.CLOSE.rawValue { // do nothing return @@ -445,29 +445,29 @@ public class SocketEngine: NSObject, WebSocketDelegate { // println("Got something idk what to do with") // println(messageString) } - + // Remove message type message.removeAtIndex(message.startIndex) - + dispatch_async(self.client.handleQueue) {[weak self] in self?.client.parseSocketMessage(message) return } } - + private func probeWebSocket() { if self.websocketConnected { self.sendWebSocketMessage("probe", withType: PacketType.PING) } } - + public func send(msg:String, datas:[NSData]? = nil) { let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { if self == nil || !self!.connected { return } - + if self!.websocket { // println("sending ws: \(msg):\(datas)") self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) @@ -477,12 +477,12 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + dispatch_async(self.emitQueue) {[weak self] in if self == nil { return } - + if self!.probing { self?.probeWait.append(_send(msg, datas)) } else { @@ -490,7 +490,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + func sendPing() { if self.websocket { self.sendWebSocketMessage("", withType: PacketType.PING) @@ -498,31 +498,31 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.sendPollMessage("", withType: PacketType.PING) } } - + 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!) } } - + if !self.waitingForPost { self.flushWaitingForPost() } } - + private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending ws: \(str) as type: \(type.rawValue)") self.ws?.writeString("\(type.rawValue)\(str)") - + if datas != nil { for data in datas! { let (data, nilString) = self.createBinaryDataForSend(data) @@ -532,20 +532,20 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + // 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) } } - + private func upgradeTransport() { if self.websocketConnected { // Do a fast upgrade @@ -554,17 +554,17 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.sendPollMessage("", withType: PacketType.NOOP) } } - + public func websocketDidConnect(socket:WebSocket) { self.websocketConnected = true self.probing = true self.probeWebSocket() } - + public func websocketDidDisconnect(socket:WebSocket, error:NSError?) { self.websocketConnected = false self.probing = false - + if self.websocket { self.pingTimer?.invalidate() self._connected = false @@ -575,11 +575,11 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.flushProbeWait() } } - + public func websocketDidReceiveMessage(socket:WebSocket, text:String) { self.parseEngineMessage(text) } - + public func websocketDidReceiveData(socket:WebSocket, data:NSData) { self.parseEngineData(data) } diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index d5de23b..48660c7 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -32,7 +32,7 @@ class SocketEvent { lazy var datas = [NSData]() var event:String! var placeholders:Int! - + init(event:String, args:AnyObject?, placeholders:Int = 0, ackNum:Int? = nil, justAck:Bool = false) { self.event = event self.args = args @@ -40,7 +40,7 @@ class SocketEvent { self.ack = ackNum self.justAck = justAck } - + func addData(data:NSData) -> Bool { func checkDoEvent() -> Bool { if self.placeholders == self.currentPlace { @@ -49,14 +49,14 @@ class SocketEvent { return false } } - + if checkDoEvent() { return true } - + self.datas.append(data) self.currentPlace++ - + if checkDoEvent() { self.currentPlace = 0 return true @@ -64,13 +64,13 @@ class SocketEvent { return false } } - + class func createMessageForEvent(event:String, withArgs args:[AnyObject], 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 { if ack == nil { @@ -100,14 +100,14 @@ class SocketEvent { } } } - + return self.completeMessage(message, args: args) } - + 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 = "3\(ack)[" @@ -121,52 +121,52 @@ class SocketEvent { msg = "6\(binary)-/\(nsp),\(ack)[" } } - + return self.completeMessage(msg, args: args, ack: true) } - + 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 { - + 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 += jsonString! as String message += "," continue } - + 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 { var newArr = [AnyObject](count: arr.count, repeatedValue: 0) // println(arr) - + for i in 0.. NSDictionary { var newDict = [String: AnyObject]() - + for (key, value) in dict { newDict[key as! String] = value - + // If the value is a string we need to check // if it is a placeholder for data if let str = value as? String { if let num = str["~~(\\d)"].groups() { - newDict[key as String] = self.datas[num[1].toInt()!] + newDict[key as! String] = self.datas[num[1].toInt()!] } else { newDict[key as! String] = str } @@ -206,10 +206,10 @@ class SocketEvent { newDict[key as! String] = self.fillInArray(arr) } } - + return newDict } - + func fillInPlaceholders(_ args:AnyObject = true) -> AnyObject { if let dict = args as? NSDictionary { return self.fillInDict(dict) @@ -225,7 +225,7 @@ class SocketEvent { let argsAsArray = "[\(self.args)]" if let parsedArr = SocketParser.parseData(argsAsArray) as? NSArray { var returnArr = [AnyObject](count: parsedArr.count, repeatedValue: 0) - + for i in 0.. AnyObject? { if data == nil { return nil } - + var err:NSError? let stringData = data!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) let parsed:AnyObject? = NSJSONSerialization.JSONObjectWithData(stringData!, options: NSJSONReadingOptions.AllowFragments, error: &err) - + if err != nil { // println(err) return nil } - + return parsed } - + class func parseEmitArgs(args:[AnyObject]) -> ([AnyObject], Bool, [NSData]) { var items = [AnyObject](count: args.count, repeatedValue: 1) var currentPlaceholder = -1 var hasBinary = false var emitDatas = [NSData]() - + for i in 0.. (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false var returnDatas = [NSData]() - + for (key, value) in dict { if let binaryData = value as? NSData { currentPlaceholder++ @@ -155,7 +155,7 @@ class SocketParser { returnDict[key as! String] = ["_placeholder": true, "num": currentPlaceholder] } else if let arr = value as? NSArray { let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) - + if hadBinary { hasBinary = true returnDict[key as! String] = replace @@ -167,7 +167,7 @@ class SocketParser { } else if let dict = value as? NSDictionary { // Recursive let (nestDict, hadBinary, nestDatas) = self.parseNSDictionary(dict, currentPlaceholder: currentPlaceholder) - + if hadBinary { hasBinary = true returnDict[key as! String] = nestDict @@ -180,18 +180,18 @@ class SocketParser { returnDict[key as! String] = value } } - + return (returnDict, hasBinary, returnDatas) } - + // Parses messages recieved class func parseSocketMessage(stringMessage:String, socket:SocketIOClient) { if stringMessage == "" { return } - + // NSLog(stringMessage) - + // Check for successful namepsace connect if socket.nsp != nil { if stringMessage == "0/\(socket.nsp!)" { @@ -199,7 +199,7 @@ class SocketParser { return } } - + if stringMessage == "0" { if socket.nsp != nil { // Join namespace @@ -210,14 +210,14 @@ class SocketParser { return } } - + if stringMessage.hasPrefix("5") { // Check for message with binary placeholders self.parseBinaryMessage(stringMessage, socket: socket) return } - - + + /** Begin check for message **/ @@ -226,16 +226,16 @@ class SocketParser { NSLog("Error in groups") return } - + // let messageGroups = SwiftRegex(target: stringMessage as NSString, // pattern: "(\\d*)\\/?(\\w*)?,?(\\d*)?\\[\"(.*?)\",?(.*?)?\\]$", // options: NSRegularExpressionOptions.DotMatchesLineSeparators).groups() - + if messageGroups![1].hasPrefix("2") { var mesNum = messageGroups![1] var ackNum:String var namespace:String? - + if messageGroups![3] != "" { ackNum = messageGroups![3] } else { @@ -244,16 +244,16 @@ class SocketParser { mesNum.replaceRange(range, with: "") ackNum = mesNum } - + namespace = messageGroups![2] - + if namespace == "" && socket.nsp != nil { return } - + let event = messageGroups![4] let data = "[\(messageGroups![5])]" - + if let parsed:AnyObject = self.parseData(data) { if ackNum == "" { socket.handleEvent(event, data: parsed) @@ -262,53 +262,53 @@ class SocketParser { socket.handleEvent(event, data: parsed, 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 == "" && socket.nsp != nil { return } - + if nsp == "" { ackNum = String(arr[1...arr.count-1]) } else { ackNum = messageGroups![3] } - + let ackData:AnyObject? = self.parseData(messageGroups![4]) socket.handleAck(ackNum.toInt()!, data: ackData) - + return } /** End Check for message **/ } - + // Handles binary data class func parseBinaryData(data:NSData, socket:SocketIOClient) { // NSLog(data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)) let shouldExecute = socket.waitingData[0].addData(data) - + if shouldExecute { let socketEvent = socket.waitingData.removeAtIndex(0) var event = socketEvent.event var parsedArgs:AnyObject? = self.parseData(socketEvent.args as? String) - + if let args:AnyObject = parsedArgs { let filledInArgs:AnyObject = socketEvent.fillInPlaceholders(args) - + if socketEvent.justAck! { // Should handle ack socket.handleAck(socketEvent.ack!, data: filledInArgs) return } - + // Should do event if socketEvent.ack != nil { socket.handleEvent(event, data: filledInArgs, isInternalMessage: false, @@ -318,13 +318,13 @@ class SocketParser { } } else { let filledInArgs:AnyObject = socketEvent.fillInPlaceholders() - + // Should handle ack if socketEvent.justAck! { socket.handleAck(socketEvent.ack!, data: filledInArgs) return } - + // Should handle ack if socketEvent.ack != nil { socket.handleEvent(event, data: filledInArgs, isInternalMessage: false, @@ -335,11 +335,11 @@ class SocketParser { } } } - + // Tries to parse a message that contains binary class func parseBinaryMessage(message:String, socket:SocketIOClient) { // NSLog(message) - + /** Begin check for binary placeholders **/ @@ -347,11 +347,11 @@ class SocketParser { // let binaryGroup = SwiftRegex(target: message, // pattern: "^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$", // options: NSRegularExpressionOptions.DotMatchesLineSeparators).groups() - + if binaryGroup == nil { return } - + if binaryGroup![1].hasPrefix("5") { // println(binaryGroup) var ackNum:String @@ -359,9 +359,9 @@ class SocketParser { var mutMessageObject:String var namespace:String? var numberOfPlaceholders:String - + let messageType = binaryGroup![1] - + namespace = binaryGroup![2] if binaryGroup![3] != "" { ackNum = binaryGroup![3] as String @@ -370,18 +370,18 @@ class SocketParser { } else { ackNum = "" } - + numberOfPlaceholders = (messageType["5"] ~= "") as String event = (binaryGroup![4]["\""] ~= "") as String mutMessageObject = binaryGroup![5] - + if namespace == "" && socket.nsp != nil { return } - + let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - + var mes:SocketEvent if ackNum == "" { mes = SocketEvent(event: event, args: placeholdersRemoved, @@ -391,14 +391,14 @@ class SocketParser { mes = SocketEvent(event: event, args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) } - + socket.waitingData.append(mes) } else if binaryGroup![1].hasPrefix("6") { let messageType = binaryGroup![1] let numberOfPlaceholders = (messageType["6"] ~= "") as String var ackNum:String var nsp:String - + if binaryGroup![3] == "" { ackNum = binaryGroup![2] nsp = "" @@ -406,17 +406,17 @@ class SocketParser { ackNum = binaryGroup![3] nsp = binaryGroup![2] } - + if nsp == "" && socket.nsp != nil { return } var mutMessageObject = binaryGroup![5] let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - + let event = SocketEvent(event: "", args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) - + socket.waitingData.append(event) } /** diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index fddcbf9..231a661 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -18,7 +18,7 @@ var swiftRegexCache = [String: NSRegularExpression]() public class SwiftRegex: NSObject, BooleanType { var target:String var regex: NSRegularExpression - + init(target:String, pattern:String, options:NSRegularExpressionOptions = nil) { self.target = target if let regex = swiftRegexCache[pattern] { @@ -36,16 +36,16 @@ public class SwiftRegex: NSObject, BooleanType { } super.init() } - + class func failure(message: String) { println("SwiftRegex: "+message) //assert(false,"SwiftRegex: failed") } - + final var targetRange: NSRange { - return NSRange(location: 0,length: countElements(target)) + return NSRange(location: 0,length: count(target)) } - + final func substring(range: NSRange) -> String? { if ( range.location != NSNotFound ) { return (target as NSString).substringWithRange(range) @@ -53,23 +53,23 @@ public class SwiftRegex: NSObject, BooleanType { return nil } } - + public func doesMatch(options: NSMatchingOptions = nil) -> Bool { return range(options: options).location != NSNotFound } - + public func range(options: NSMatchingOptions = nil) -> NSRange { return regex.rangeOfFirstMatchInString(target as String, options: nil, range: targetRange) } - + public func match(options: NSMatchingOptions = nil) -> String? { return substring(range(options: options)) } - + public func groups(options: NSMatchingOptions = nil) -> [String]? { return groupsForMatch(regex.firstMatchInString(target as String, options: options, range: targetRange)) } - + func groupsForMatch(match: NSTextCheckingResult!) -> [String]? { if match != nil { var groups = [String]() @@ -85,51 +85,51 @@ public class SwiftRegex: NSObject, BooleanType { return nil } } - + public subscript(groupno: Int) -> String? { get { return groups()?[groupno] } - + set(newValue) { if newValue == nil { return } - + for match in matchResults()!.reverse() { let replacement = regex.replacementStringForResult(match, inString: target as String, offset: 0, template: newValue!) let mut = NSMutableString(string: target) mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) - - target = mut + + target = mut as String } } } - + 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 } } - + public func matches(options: NSMatchingOptions = nil) -> [String] { return matchResults(options: options)!.map( { self.substring($0.range)!}) } - + public func allGroups(options: NSMatchingOptions = nil) -> [[String]?] { return matchResults(options: options)!.map {self.groupsForMatch($0)} } - + public func dictionary(options: NSMatchingOptions = nil) -> Dictionary { var out = Dictionary() for match in matchResults(options: options)! { @@ -137,26 +137,26 @@ public class SwiftRegex: NSObject, BooleanType { } return out } - + func substituteMatches(substitution: (NSTextCheckingResult, UnsafeMutablePointer) -> String, options:NSMatchingOptions = nil) -> String { let out = NSMutableString() var pos = 0 - + 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( substitution(match, stop) ) pos = matchRange.location + matchRange.length } - + out.appendString(substring( NSRange(location:pos, length:targetRange.length-pos))!) - - return out + + return out as String } - + public var boolValue: Bool { return doesMatch() } @@ -184,11 +184,11 @@ public func ~= (left: SwiftRegex, right: String) -> String { public func ~= (left: SwiftRegex, right: [String]) -> String { var matchNumber = 0 return left.substituteMatches({match, stop -> String in - + if ++matchNumber == right.count { stop.memory = true } - + return left.regex.replacementStringForResult( match, inString: left.target as String, offset: 0, template: right[matchNumber-1] ) }, options: nil) From 3e260152cbf8fbd39c570a17c3a9cf637f2707c3 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 16 Mar 2015 16:30:28 -0400 Subject: [PATCH 28/42] update for 1.2 --- SwiftIO/SocketFixUTF8.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/SwiftIO/SocketFixUTF8.swift b/SwiftIO/SocketFixUTF8.swift index 0d10fcc..3d325e6 100644 --- a/SwiftIO/SocketFixUTF8.swift +++ b/SwiftIO/SocketFixUTF8.swift @@ -27,7 +27,7 @@ import Foundation var memoizer = [String: UnicodeScalar]() -func lookup(base:UnicodeScalar, combi:UnicodeScalar) -> UnicodeScalar { +private func lookup(base:UnicodeScalar, combi:UnicodeScalar) -> UnicodeScalar { let combined = "\(base)\(combi)" if let y = memoizer[combined] { @@ -54,13 +54,13 @@ func fixDoubleUTF8(inout name:String) { for ch in name.unicodeScalars { if ch.value < 0x80 { - y.append(UInt8(ch)) + y.append(UInt8(ch.value)) continue } isASCII = false if ch.value < 0x100 { - y.append(UInt8(ch)) + y.append(UInt8(ch.value)) continue } // might be a combining character that when combined with the @@ -77,7 +77,7 @@ func fixDoubleUTF8(inout name:String) { return } - y.append(UInt8(repl)) + y.append(UInt8(repl.value)) } if isASCII { @@ -92,7 +92,11 @@ func fixDoubleUTF8(inout name:String) { if let str = rslt.0 { if !rslt.hadError { name = str + } else { + println("error") } + } else { + println("error") } return From 3ebdd9f7ebe122c8d553e207b794c87315b5e2a2 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 16 Mar 2015 17:43:54 -0400 Subject: [PATCH 29/42] fix #26 --- SwiftIO/SocketParser.swift | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/SwiftIO/SocketParser.swift b/SwiftIO/SocketParser.swift index e094f26..b59929f 100644 --- a/SwiftIO/SocketParser.swift +++ b/SwiftIO/SocketParser.swift @@ -211,7 +211,7 @@ class SocketParser { } } - if stringMessage.hasPrefix("5") { + if stringMessage.hasPrefix("5") || stringMessage.hasPrefix("6") { // Check for message with binary placeholders self.parseBinaryMessage(stringMessage, socket: socket) return @@ -220,10 +220,16 @@ class SocketParser { /** Begin check for message **/ - let messageGroups = stringMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?\\[\"(.*?)\",?(.*?)?\\]$", - NSRegularExpressionOptions.DotMatchesLineSeparators].groups() - if messageGroups == nil { - NSLog("Error in groups") + var messageGroups:[String]? + + if let groups = stringMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?\\[\"(.*?)\",?(.*?)?\\]$", + NSRegularExpressionOptions.DotMatchesLineSeparators].groups() { + messageGroups = groups + } else if let ackGroup = stringMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?\\[(.*?)?\\]$", + NSRegularExpressionOptions.DotMatchesLineSeparators].groups() { + messageGroups = ackGroup + } else { + NSLog("Error parsing message: %s", stringMessage) return } @@ -289,6 +295,12 @@ class SocketParser { // Handles binary data class func parseBinaryData(data:NSData, socket:SocketIOClient) { // NSLog(data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.allZeros)) + + if socket.waitingData.count == 0 { + NSLog("Got data when not remaking packet") + return + } + let shouldExecute = socket.waitingData[0].addData(data) if shouldExecute { @@ -339,10 +351,16 @@ class SocketParser { /** Begin check for binary placeholders **/ - let binaryGroup = message["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$", - NSRegularExpressionOptions.DotMatchesLineSeparators].groups() + var binaryGroup:[String]? - if binaryGroup == nil { + if let groups = message["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(\".*?\")?,?(.*)?\\]$", + NSRegularExpressionOptions.DotMatchesLineSeparators].groups() { + binaryGroup = groups + } else if let groups = message["^(\\d*)-\\/?(\\w*)?,?(\\d*)?\\[(.*?)?\\]$", + NSRegularExpressionOptions.DotMatchesLineSeparators].groups() { + binaryGroup = groups + } else { + NSLog("Error in parsing binary message: %s", message) return } From 457a33c677550d02ed522f03a6bbede7a557fbc9 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 19 Mar 2015 08:31:30 -0400 Subject: [PATCH 30/42] fix for 1.2 --- SwiftIO/SwiftRegex.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 566b774..47095f4 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -43,7 +43,7 @@ public class SwiftRegex: NSObject, BooleanType { } final var targetRange: NSRange { - return NSRange(location: 0,length: countElements(target.utf16)) + return NSRange(location: 0,length: count(target.utf16)) } final func substring(range: NSRange) -> String? { From 8b3e4a2c43f6b8b838c6f36c95e40a1102970bca Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 21 Mar 2015 11:44:49 -0400 Subject: [PATCH 31/42] fix for 1.2 --- SwiftIO/SocketPacket.swift | 4 ++-- SwiftIO/SocketParser.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SwiftIO/SocketPacket.swift b/SwiftIO/SocketPacket.swift index 56668f3..cfcbad6 100644 --- a/SwiftIO/SocketPacket.swift +++ b/SwiftIO/SocketPacket.swift @@ -68,7 +68,7 @@ class SocketPacket { } func getEvent() -> String { - return data?.removeAtIndex(0) as String + return data?.removeAtIndex(0) as! String } func addData(data:NSData) -> Bool { @@ -226,7 +226,7 @@ class SocketPacket { // if it is a placeholder for data if let str = value as? String { if let num = str["~~(\\d)"].groups() { - newDict[key as! String] = self.datas[num[1].toInt()!] + newDict[key as! String] = self.binary[num[1].toInt()!] } else { newDict[key as! String] = str } diff --git a/SwiftIO/SocketParser.swift b/SwiftIO/SocketParser.swift index 923d210..20fbe9e 100644 --- a/SwiftIO/SocketParser.swift +++ b/SwiftIO/SocketParser.swift @@ -93,7 +93,7 @@ class SocketParser { let d = String(arr[++i...arr.count-1]) let noPlaceholders = d["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - let data = SocketParser.parseData(noPlaceholders) as [AnyObject] + let data = SocketParser.parseData(noPlaceholders) as! [AnyObject] return SocketPacket(type: SocketPacketType(str: type), data: data, nsp: nsp, placeholders: placeholders, id: id) From 5e1a19f63866a6137cd0e2be309ac5bb0b2fd7e3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 22 Mar 2015 15:12:01 -0400 Subject: [PATCH 32/42] fix for 1.2 --- SwiftIO/SocketParser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftIO/SocketParser.swift b/SwiftIO/SocketParser.swift index 3b68e51..743765c 100644 --- a/SwiftIO/SocketParser.swift +++ b/SwiftIO/SocketParser.swift @@ -48,7 +48,7 @@ class SocketParser { var newDict = NSMutableDictionary(dictionary: dict) for (key, value) in newDict { - newDict[key as NSCopying] = shred(value) + newDict[key as! NSCopying] = shred(value) } return newDict From 03c24659f23df284db9e86a1d1498391c3feb095 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 22 Mar 2015 15:13:44 -0400 Subject: [PATCH 33/42] use static property --- SwiftIO/SocketParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketParser.swift b/SwiftIO/SocketParser.swift index 743765c..064dcac 100644 --- a/SwiftIO/SocketParser.swift +++ b/SwiftIO/SocketParser.swift @@ -22,9 +22,9 @@ import Foundation -private let shredder = SocketParser.PacketShredder() - class SocketParser { + private static let shredder = SocketParser.PacketShredder() + // Translation of socket.io-parser#deconstructPacket private class PacketShredder { var buf = ContiguousArray() From d308111a8fd4529bf3971632322acf2da56dff20 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 27 Mar 2015 11:35:02 -0400 Subject: [PATCH 34/42] fix for 1.2 --- SwiftIO/SocketPacket.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftIO/SocketPacket.swift b/SwiftIO/SocketPacket.swift index d08435c..88bd394 100644 --- a/SwiftIO/SocketPacket.swift +++ b/SwiftIO/SocketPacket.swift @@ -202,7 +202,7 @@ class SocketPacket { } } - self.data = newArr + self.data = newArr as [AnyObject] } private func _fillInPlaceholders(data:AnyObject) -> AnyObject { From 63cbf494481689a7062d7515eb23677db2d59f71 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 4 Apr 2015 10:47:53 -0400 Subject: [PATCH 35/42] fixes --- SwiftIO/SocketIOClient.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index b593ac8..1621162 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -195,7 +195,7 @@ public class SocketIOClient: NSObject, SocketEngineClient { return } - self?.ackHandlers.addAck(ack, callback) + self?.ackHandlers.addAck(ack, callback: callback) dispatch_async(self!.emitQueue) { self?._emit(event, items, ack: ack) @@ -343,7 +343,7 @@ public class SocketIOClient: NSObject, SocketEngineClient { var ackData:[AnyObject]? if data is NSArray { - ackData = data as? NSArray + ackData = (data as? [AnyObject]?)! } else if data != nil { ackData = [data!] } From 0c3e25ef3c9b0eb1dedab15cd1c74fedbba251fc Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 7 Apr 2015 21:58:35 -0400 Subject: [PATCH 36/42] bump version --- Socket.IO-Client-Swift.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index 9086555..dd9ed11 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.5.1" + s.version = "1.5.2" 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.5.1' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v1.5.2' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files From acdb29dca47d0ceb849dbd808fd9eba3587a4322 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Apr 2015 11:10:35 -0400 Subject: [PATCH 37/42] add message about @objc_block --- SwiftIO/SocketAckMap.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SwiftIO/SocketAckMap.swift b/SwiftIO/SocketAckMap.swift index 2a6ca81..9b7905b 100644 --- a/SwiftIO/SocketAckMap.swift +++ b/SwiftIO/SocketAckMap.swift @@ -24,6 +24,10 @@ import Foundation +// @objc_block is undocumented, but is used because Swift assumes that all +// Objective-C blocks are copied, but Objective-C assumes that Swift will copy it. +// And the way things are done here, the bridging fails to copy the block in +// SocketAckMap#addAck public typealias AckCallback = @objc_block (NSArray?) -> Void public typealias OnAckCallback = (timeout:UInt64, callback:AckCallback) -> Void From c3257062289f4b5f3c74fc871c867737e950067e Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Apr 2015 17:12:27 -0400 Subject: [PATCH 38/42] Release for Swift 1.2. For Swift 1.1 use version v1.5.2 --- Socket.IO-Client-Swift.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Socket.IO-Client-Swift.podspec b/Socket.IO-Client-Swift.podspec index dd9ed11..128e147 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.5.2" + s.version = "2.0.0" 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.5.2' } + s.source = { :git => "https://github.com/socketio/socket.io-client-swift.git", :tag => 'v2.0.0' } s.source_files = "SwiftIO/**/*.swift" s.requires_arc = true # s.dependency 'Starscream', '~> 0.9' # currently this repo includes Starscream swift files From 31a41072c34d052a302d2fb97f2058e6f69d4ab3 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Apr 2015 17:14:56 -0400 Subject: [PATCH 39/42] fix warning --- SwiftIO/SwiftRegex.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftIO/SwiftRegex.swift b/SwiftIO/SwiftRegex.swift index 47095f4..badd5e1 100644 --- a/SwiftIO/SwiftRegex.swift +++ b/SwiftIO/SwiftRegex.swift @@ -138,7 +138,7 @@ public class SwiftRegex: NSObject, BooleanType { return out } - func substituteMatches(substitution: (NSTextCheckingResult, UnsafeMutablePointer) -> String, + func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), options:NSMatchingOptions = nil) -> String { let out = NSMutableString() var pos = 0 From 903c578e753aa29c032e46de67d9f389f81e3a72 Mon Sep 17 00:00:00 2001 From: Erik Little Date: Wed, 8 Apr 2015 20:31:48 -0400 Subject: [PATCH 40/42] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index aeed460..cf6ed0c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8 }); }]; +[socket connect]; + ``` ##Features From eb5d03c88b21e7dfda3817923aeeb4e0acac5d4e Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Apr 2015 20:51:44 -0400 Subject: [PATCH 41/42] redo types --- SwiftIO/SocketAckMap.swift | 7 ----- SwiftIO/SocketEngine.swift | 26 ++++++++-------- SwiftIO/SocketEventHandler.swift | 3 -- SwiftIO/SocketFixUTF8.swift | 1 + SwiftIO/SocketIOClient.swift | 4 +-- SwiftIO/SocketPacket.swift | 20 +----------- SwiftIO/SocketTypes.swift | 52 ++++++++++++++++++++++++++++++++ 7 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 SwiftIO/SocketTypes.swift diff --git a/SwiftIO/SocketAckMap.swift b/SwiftIO/SocketAckMap.swift index 9b7905b..6e7f932 100644 --- a/SwiftIO/SocketAckMap.swift +++ b/SwiftIO/SocketAckMap.swift @@ -24,13 +24,6 @@ import Foundation -// @objc_block is undocumented, but is used because Swift assumes that all -// Objective-C blocks are copied, but Objective-C assumes that Swift will copy it. -// And the way things are done here, the bridging fails to copy the block in -// SocketAckMap#addAck -public typealias AckCallback = @objc_block (NSArray?) -> Void -public typealias OnAckCallback = (timeout:UInt64, callback:AckCallback) -> Void - struct SocketAckMap { private var acks = [Int: AckCallback]() private var waiting = [Int: Bool]() diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 435b094..6046650 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -30,20 +30,10 @@ extension String { } } -private typealias Probe = (msg:String, type:PacketType, data:ContiguousArray?) -private typealias ProbeWaitQueue = [Probe] - -public enum PacketType:String { - case OPEN = "0" - case CLOSE = "1" - case PING = "2" - case PONG = "3" - case MESSAGE = "4" - case UPGRADE = "5" - case NOOP = "6" -} - public class SocketEngine: NSObject, WebSocketDelegate { + private typealias Probe = (msg:String, type:PacketType, data:ContiguousArray?) + private typealias ProbeWaitQueue = [Probe] + private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( "engineEmitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -83,6 +73,16 @@ public class SocketEngine: NSObject, WebSocketDelegate { return self._websocket } var ws:WebSocket? + + public enum PacketType:String { + case OPEN = "0" + case CLOSE = "1" + case PING = "2" + case PONG = "3" + case MESSAGE = "4" + case UPGRADE = "5" + case NOOP = "6" + } public init(client:SocketEngineClient, forcePolling:Bool, forceWebsockets:Bool, withCookies cookies:[NSHTTPCookie]?) { diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index f68c73d..34c3086 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -24,9 +24,6 @@ import Foundation -public typealias NormalCallback = (NSArray?, AckEmitter?) -> Void -public typealias AckEmitter = (AnyObject...) -> Void - private func emitAckCallback(socket:SocketIOClient, num:Int) // Curried (items:AnyObject...) -> Void { diff --git a/SwiftIO/SocketFixUTF8.swift b/SwiftIO/SocketFixUTF8.swift index 0c36b41..5ca3ab2 100644 --- a/SwiftIO/SocketFixUTF8.swift +++ b/SwiftIO/SocketFixUTF8.swift @@ -3,6 +3,7 @@ // Socket.IO-Swift // // Created by Erik Little on 3/16/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 diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index df1b85b..c8bf163 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -306,7 +306,7 @@ public class SocketIOClient: NSObject, SocketEngineClient { } let packet = SocketPacket(type: nil, data: args, nsp: self.nsp, id: ack) - var str:String + let str:String SocketParser.parseForEmit(packet) str = packet.createMessageForEvent(event) @@ -326,7 +326,7 @@ public class SocketIOClient: NSObject, SocketEngineClient { } let packet = SocketPacket(type: nil, data: args, nsp: self!.nsp, id: ack) - var str:String + let str:String SocketParser.parseForEmit(packet) str = packet.createAck() diff --git a/SwiftIO/SocketPacket.swift b/SwiftIO/SocketPacket.swift index 97581c1..9a13b5a 100644 --- a/SwiftIO/SocketPacket.swift +++ b/SwiftIO/SocketPacket.swift @@ -24,24 +24,6 @@ import Foundation -enum SocketPacketType:Int { - case CONNECT = 0 - case DISCONNECT = 1 - case EVENT = 2 - case ACK = 3 - case ERROR = 4 - case BINARY_EVENT = 5 - case BINARY_ACK = 6 - - init(str:String) { - if let int = str.toInt() { - self = SocketPacketType(rawValue: int)! - } else { - self = SocketPacketType(rawValue: 4)! - } - } -} - class SocketPacket { var binary = ContiguousArray() var currentPlace = 0 @@ -90,7 +72,7 @@ class SocketPacket { } func createMessageForEvent(event:String) -> String { - var message:String + let message:String var jsonSendError:NSError? if self.binary.count == 0 { diff --git a/SwiftIO/SocketTypes.swift b/SwiftIO/SocketTypes.swift new file mode 100644 index 0000000..d022a93 --- /dev/null +++ b/SwiftIO/SocketTypes.swift @@ -0,0 +1,52 @@ +// +// SocketTypes.swift +// SocketIO-Swift +// +// Created by Erik Little on 4/8/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 + +// @objc_block is undocumented, but is used because Swift assumes that all +// Objective-C blocks are copied, but Objective-C assumes that Swift will copy it. +// And the way things are done here, the bridging fails to copy the block in +// SocketAckMap#addAck +public typealias AckCallback = @objc_block (NSArray?) -> Void +public typealias AckEmitter = (AnyObject...) -> Void +public typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +public typealias OnAckCallback = (timeout:UInt64, callback:AckCallback) -> Void + +enum SocketPacketType:Int { + case CONNECT = 0 + case DISCONNECT = 1 + case EVENT = 2 + case ACK = 3 + case ERROR = 4 + case BINARY_EVENT = 5 + case BINARY_ACK = 6 + + init(str:String) { + if let int = str.toInt() { + self = SocketPacketType(rawValue: int)! + } else { + self = SocketPacketType(rawValue: 4)! + } + } +} \ No newline at end of file From 14487a1bebbfe1ffde908a6129b6cb5e1c37032d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 8 Apr 2015 21:14:52 -0400 Subject: [PATCH 42/42] use int for enum --- SwiftIO/SocketEngine.swift | 374 ++++++++++++++++++------------------- 1 file changed, 187 insertions(+), 187 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 6046650..eaca423 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -59,7 +59,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { var connected:Bool { return self._connected } - + weak var client:SocketEngineClient? var cookies:[NSHTTPCookie]? var pingInterval:Int? @@ -74,16 +74,24 @@ public class SocketEngine: NSObject, WebSocketDelegate { } var ws:WebSocket? - public enum PacketType:String { - case OPEN = "0" - case CLOSE = "1" - case PING = "2" - case PONG = "3" - case MESSAGE = "4" - case UPGRADE = "5" - case NOOP = "6" + public enum PacketType:Int { + case OPEN = 0 + case CLOSE = 1 + case PING = 2 + case PONG = 3 + case MESSAGE = 4 + case UPGRADE = 5 + case NOOP = 6 + + init(str:String) { + if let value = str.toInt() { + self = PacketType(rawValue: value)! + } else { + self = PacketType.NOOP + } + } } - + public init(client:SocketEngineClient, forcePolling:Bool, forceWebsockets:Bool, withCookies cookies:[NSHTTPCookie]?) { self.client = client @@ -93,19 +101,19 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration(), delegate: nil, delegateQueue: self.workQueue) } - + public func close(#fast:Bool) { self.pingTimer?.invalidate() self.closed = true - + self.write("", withType: PacketType.CLOSE, withData: nil) self.ws?.disconnect() - + if fast || self.polling { self.client?.didForceClose("Disconnect") } } - + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { if self.websocket { var byteArray = [UInt8](count: 1, repeatedValue: 0x0) @@ -117,20 +125,20 @@ public class SocketEngine: NSObject, WebSocketDelegate { var str = "b4" str += data.base64EncodedStringWithOptions( NSDataBase64EncodingOptions.Encoding64CharacterLineLength) - + return (nil, str) } } - + private func createURLs(params:[String: AnyObject]?) -> (String, String) { if self.client == nil { return ("", "") } - + var url = "\(self.client!.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String - + if self.client!.secure { urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" @@ -138,14 +146,14 @@ public class SocketEngine: NSObject, WebSocketDelegate { urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } - + 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())! @@ -157,26 +165,26 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + return (urlPolling, urlWebSocket) } - + private func createWebsocket(andConnect connect:Bool) { self.ws = WebSocket(url: NSURL(string: self.urlWebSocket! + "&sid=\(self.sid)")!) self.ws?.queue = self.handleQueue self.ws?.delegate = self - + if connect { self.ws?.connect() } } - + private func doFastUpgrade() { if self.waitingForPoll { NSLog("Outstanding poll when switched to websockets," + "we'll probably disconnect soon. You should report this.") } - + self.sendWebSocketMessage("", withType: PacketType.UPGRADE, datas: nil) self._websocket = true self._polling = false @@ -184,25 +192,25 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.probing = false self.flushProbeWait() } - + private func doPoll() { if self.websocket || self.waitingForPoll || !self.connected { return } - + self.waitingForPoll = true let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)&b64=1")!) - + self.doRequest(req) } - + private func doRequest(req:NSMutableURLRequest) { if !self.polling { return } - + req.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData - + // NSLog("Doing request: \(req)") self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { @@ -213,21 +221,21 @@ public class SocketEngine: NSObject, WebSocketDelegate { } else { NSLog(err.localizedDescription) } - + return } - + // NSLog("Got response: \(res)") - + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { dispatch_async(self!.parseQueue) { self?.parsePollingMessage(str) return } } - + self?.waitingForPoll = false - + if self!.fastUpgrade { self?.doFastUpgrade() return @@ -236,26 +244,26 @@ public class SocketEngine: NSObject, WebSocketDelegate { } }.resume() } - + private func flushProbeWait() { // NSLog("flushing probe wait") dispatch_async(self.emitQueue) {[weak self] in if self == nil { return } - + for waiter in self!.probeWait { self?.write(waiter.msg, withType: waiter.type, withData: waiter.data) } - + self?.probeWait.removeAll(keepCapacity: false) - + if self?.postWait.count != 0 { self?.flushWaitingForPostToWebSocket() } } } - + private func flushWaitingForPost() { if self.postWait.count == 0 || !self.connected { return @@ -263,33 +271,33 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.flushWaitingForPostToWebSocket() return } - + var postStr = "" - + for packet in self.postWait { let len = count(packet) - + postStr += "\(len):\(packet)" } - + self.postWait.removeAll(keepCapacity: false) - + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - + req.HTTPMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - + req.HTTPBody = postData req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - + self.waitingForPost = true - + // NSLog("posting: \(postStr)") // NSLog("Posting with WS status of: \(self.websocket)") - + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return @@ -300,7 +308,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { NSLog(err.localizedDescription) return } - + self?.waitingForPost = false dispatch_async(self!.emitQueue) { if !self!.fastUpgrade { @@ -309,90 +317,90 @@ public class SocketEngine: NSObject, WebSocketDelegate { } }}.resume() } - + // We had packets waiting for send when we upgraded // Send them raw private func flushWaitingForPostToWebSocket() { for msg in self.postWait { self.ws?.writeString(msg) } - + self.postWait.removeAll(keepCapacity: true) } - + // A poll failed, tell the client about it - + private func handlePollingFailed(reason:String) { self._connected = false self.ws?.disconnect() self.pingTimer?.invalidate() self.waitingForPoll = false self.waitingForPost = false - + if self.client == nil { return } - + if !self.closed && !self.client!.reconnecting { self.client?.pollingDidFail(reason) } else if !self.client!.reconnecting { self.client?.didForceClose(reason) } } - + public func open(opts:[String: AnyObject]? = nil) { if self.connected { fatalError("Engine tried to open while connected") } - + self.closed = false let (urlPolling, urlWebSocket) = self.createURLs(opts) self.urlPolling = urlPolling self.urlWebSocket = urlWebSocket - + if self.forceWebsockets { self._polling = false self._websocket = true self.createWebsocket(andConnect: true) return } - + let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) - + if self.cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(self.cookies!) reqPolling.allHTTPHeaderFields = headers } - + self.doRequest(reqPolling) } - + // Translatation of engine.io-parser#decodePayload private func parsePollingMessage(str:String) { if str.length == 1 { return } - + // println(str) - + let strArray = Array(str) var length = "" var n = 0 var msg = "" - + func testLength(length:String, inout n:Int) -> Bool { if let num = length.toInt() { n = num } else { return true } - + return false } - + for var i = 0, l = str.length; i < l; i = i &+ 1 { let chr = String(strArray[i]) - + if chr != ":" { length += chr } else { @@ -401,16 +409,16 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.handlePollingFailed("Error parsing XHR message") return } - + msg = String(strArray[i&+1...i&+n]) - + if let lengthInt = length.toInt() { if lengthInt != msg.length { NSLog("parsing error: \(str)") return } } - + if msg.length != 0 { // Be sure to capture the value of the msg dispatch_async(self.handleQueue) {[weak self, msg] in @@ -418,134 +426,126 @@ public class SocketEngine: NSObject, WebSocketDelegate { return } } - + i += n length = "" } } } - + private func parseEngineData(data:NSData) { if self.client == nil { return } - + dispatch_async(self.client!.handleQueue) {[weak self] in self?.client?.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) return } } - + private func parseEngineMessage(var message:String, fromPolling:Bool) { // NSLog("Engine got message: \(message)") if fromPolling { fixDoubleUTF8(&message) } - - let type = message["^(\\d)"].groups()?[1] - - if type != PacketType.MESSAGE.rawValue { - // TODO Handle other packets + + let type = PacketType(str: (message["^(\\d)"].groups()?[1])!) + + if type == PacketType.MESSAGE { + // Remove message type + message.removeAtIndex(message.startIndex) + + if self.client == nil { + return + } + + dispatch_async(self.client!.handleQueue) {[weak self] in + self?.client?.parseSocketMessage(message) + return + } + } else if type == PacketType.NOOP { + self.doPoll() + return + } else if type == PacketType.PONG { + // We should upgrade + if message == "3probe" { + self.upgradeTransport() + return + } + + return + } else if type == PacketType.OPEN { + var err:NSError? + + message.removeAtIndex(message.startIndex) + let mesData = message.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + + if let json = NSJSONSerialization.JSONObjectWithData(mesData, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + if let sid = json["sid"] as? String { + // println(json) + self.sid = sid + self._connected = true + if !self.forcePolling && !self.forceWebsockets { + self.createWebsocket(andConnect: true) + } + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self.pingInterval = pingInterval / 1000 + } + } else { + fatalError("Error parsing engine connect") + } + + self.startPingTimer() + + if !self.forceWebsockets { + self.doPoll() + } + + return + } else if type == PacketType.CLOSE { + if self.client == nil { + return + } + + if self.polling { + self.client!.didForceClose("Disconnect") + } + + return + } else { 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) { + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) + where self.client != nil { // println("sending \(data)") - - if self.client == nil { - return - } - + dispatch_async(self.client!.handleQueue) {[weak self] in self?.client?.parseBinaryData(data) return } } - - return - } else if type == PacketType.NOOP.rawValue { - self.doPoll() - return - } else if type == PacketType.PONG.rawValue { - // We should upgrade - if message == "3probe" { - self.upgradeTransport() - return - } - - return - } else if type == PacketType.OPEN.rawValue { - var err:NSError? - - message.removeAtIndex(message.startIndex) - let mesData = message.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - - if let json = NSJSONSerialization.JSONObjectWithData(mesData, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - // println(json) - self.sid = sid - self._connected = true - if !self.forcePolling && !self.forceWebsockets { - self.createWebsocket(andConnect: true) - } - } else { - NSLog("Error handshaking") - return - } - - if let pingInterval = json["pingInterval"] as? Int { - self.pingInterval = pingInterval / 1000 - } - } else { - fatalError("Error parsing engine connect") - } - - self.startPingTimer() - - if !self.forceWebsockets { - self.doPoll() - } - - return - } else if type == PacketType.CLOSE.rawValue { - if self.client == nil { - return - } - - if self.polling { - self.client!.didForceClose("Disconnect") - } - - return } - // println("Got something idk what to do with") - // println(messageString) - } - - // Remove message type - message.removeAtIndex(message.startIndex) - - if self.client == nil { - return - } - - dispatch_async(self.client!.handleQueue) {[weak self] in - self?.client?.parseSocketMessage(message) - return } } - + private func probeWebSocket() { if self.websocketConnected { self.sendWebSocketMessage("probe", withType: PacketType.PING) } } - + /// Send an engine message (4) public func send(msg:String, withData datas:ContiguousArray?) { if self.probing { @@ -554,11 +554,11 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.write(msg, withType: PacketType.MESSAGE, withData: datas) } } - + func sendPing() { self.write("", withType: PacketType.PING, withData: nil) } - + /// Send polling message. /// Only call on emitQueue private func sendPollMessage(var msg:String, withType type:PacketType, @@ -566,29 +566,29 @@ public class SocketEngine: NSObject, WebSocketDelegate { // println("Sending poll: \(msg) as type: \(type.rawValue)") doubleEncodeUTF8(&msg) 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.waitingForPost { self.flushWaitingForPost() } } - + /// Send message on WebSockets /// Only call on emitQueue private func sendWebSocketMessage(str:String, withType type:PacketType, datas:ContiguousArray? = nil) { // println("Sending ws: \(str) as type: \(type.rawValue)") self.ws?.writeString("\(type.rawValue)\(str)") - + if datas != nil { for data in datas! { let (data, nilString) = self.createBinaryDataForSend(data) @@ -598,13 +598,13 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + // 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!), @@ -612,7 +612,7 @@ public class SocketEngine: NSObject, WebSocketDelegate { selector: Selector("sendPing"), userInfo: nil, repeats: true) } } - + private func upgradeTransport() { if self.websocketConnected { // NSLog("Doing fast upgrade") @@ -622,13 +622,13 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.sendPollMessage("", withType: PacketType.NOOP) } } - + public func write(msg:String, withType type:PacketType, withData data:ContiguousArray?) { dispatch_async(self.emitQueue) {[weak self] in if self == nil || !self!.connected { return } - + if self!.websocket { // NSLog("writing ws: \(msg):\(data)") self?.sendWebSocketMessage(msg, withType: type, datas: data) @@ -638,12 +638,12 @@ public class SocketEngine: NSObject, WebSocketDelegate { } } } - + // Delagate methods - + public func websocketDidConnect(socket:WebSocket) { self.websocketConnected = true - + if !self.forceWebsockets { self.probing = true self.probeWebSocket() @@ -653,21 +653,21 @@ public class SocketEngine: NSObject, WebSocketDelegate { self._polling = false } } - + public func websocketDidDisconnect(socket:WebSocket, error:NSError?) { self.websocketConnected = false self.probing = false - + if self.closed { self.client?.didForceClose("Disconnect") return } - + if self.websocket { self.pingTimer?.invalidate() self._connected = false self._websocket = false - + let reason = error?.localizedDescription self.client?.webSocketDidCloseWithCode(1, reason: reason == nil ? "Socket Disconnected" : reason!) @@ -675,11 +675,11 @@ public class SocketEngine: NSObject, WebSocketDelegate { self.flushProbeWait() } } - + public func websocketDidReceiveMessage(socket:WebSocket, text:String) { self.parseEngineMessage(text, fromPolling: false) } - + public func websocketDidReceiveData(socket:WebSocket, data:NSData) { self.parseEngineData(data) }