add new things to 1.2

This commit is contained in:
Erik 2015-03-06 23:41:15 -05:00
parent 02b1314a3d
commit 1ac05d9790
6 changed files with 837 additions and 276 deletions

View File

@ -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

View File

@ -24,7 +24,7 @@
import Foundation
typealias AckCallback = (AnyObject?) -> Void
typealias AckCallback = (NSArray?) -> Void
class SocketAckHandler {
let ackNum:Int!

596
SwiftIO/SocketEngine.swift Normal file
View File

@ -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<String.Index>(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()
}
}
}

View File

@ -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)["
}
}

View File

@ -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
}
}
}

View File

@ -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..<arr.count {
if arr[g] is NSData {
hasBinary = true
let sendData = self.createBinaryDataForSend(arr[g] as! NSData)
currentPlaceholder++
let sendData = arr[g] as! NSData
arrayDatas.append(sendData)
replacementArr[g] = ["_placeholder": true,
"num": placeholders++]
"num": currentPlaceholder]
} else if let dict = arr[g] as? NSDictionary {
let (nestDict, hadBinary, dictArrs) = self.parseNSDictionary(dict, placeholders: placeholders)
let (nestDict, hadBinary, dictArrs) = self.parseNSDictionary(dict,
currentPlaceholder: currentPlaceholder)
if hadBinary {
hasBinary = true
placeholders += dictArrs.count
currentPlaceholder += dictArrs.count
replacementArr[g] = nestDict
arrayDatas.extend(dictArrs)
} else {
@ -361,11 +363,12 @@ class SocketIOClient: NSObject, SRWebSocketDelegate {
}
} else if let nestArr = arr[g] as? NSArray {
// Recursive
let (nested, hadBinary, nestDatas) = self.parseArray(nestArr, placeholders: placeholders)
let (nested, hadBinary, nestDatas) = self.parseArray(nestArr,
currentPlaceholder: currentPlaceholder)
if hadBinary {
hasBinary = true
placeholders += nestDatas.count
currentPlaceholder += nestDatas.count
replacementArr[g] = nested
arrayDatas.extend(nestDatas)
} else {
@ -400,7 +403,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate {
private class func parseEmitArgs(args:[AnyObject]) -> ([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<String.Index>(start: mesNum.startIndex, end: advance(mesNum.startIndex, 2))
let range = Range<String.Index>(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()
}
}
}
}