From c0ab2443ffc15ebf764d02a3558fadbb37907f2a Mon Sep 17 00:00:00 2001 From: Greg Crabtree Date: Thu, 12 May 2016 16:01:46 -0500 Subject: [PATCH 1/5] In progress work. --- RNSwiftSocketIO/Socket.swift | 12 +- RNSwiftSocketIO/SocketIOClient/.DS_Store | Bin 0 -> 6148 bytes .../SocketIOClient/NSCharacterSet.swift | 31 + .../SocketIOClient/SocketAckEmitter.swift | 6 +- .../SocketIOClient/SocketAckManager.swift | 2 +- .../SocketIOClient/SocketAnyEvent.swift | 4 +- .../SocketIOClient/SocketEngine.swift | 837 +++++++--------- .../SocketIOClient/SocketEngineClient.swift | 12 +- ...TF8.swift => SocketEnginePacketType.swift} | 18 +- .../SocketIOClient/SocketEnginePollable.swift | 236 +++++ .../SocketIOClient/SocketEngineSpec.swift | 115 +++ .../SocketEngineWebsocket.swift | 62 ++ .../SocketIOClient/SocketEventHandler.swift | 17 +- .../SocketIOClient/SocketIOClient.swift | 646 ++++++------- .../SocketIOClient/SocketIOClientOption.swift | 220 +++++ .../SocketIOClient/SocketIOClientSpec.swift | 43 + .../SocketIOClient/SocketIOClientStatus.swift | 24 +- .../SocketIOClient/SocketLogger.swift | 14 +- .../SocketIOClient/SocketPacket.swift | 230 ++--- .../SocketIOClient/SocketParsable.swift | 182 ++++ .../SocketIOClient/SocketParser.swift | 175 ---- .../SocketIOClient/SocketStringReader.swift | 2 +- .../SocketIOClient/SocketTypes.swift | 6 +- RNSwiftSocketIO/SocketIOClient/String.swift | 31 + .../SocketIOClient/SwiftRegex.swift | 56 +- .../SocketIOClient/WebSocket.swift | 915 ++++++++++-------- 26 files changed, 2221 insertions(+), 1675 deletions(-) create mode 100644 RNSwiftSocketIO/SocketIOClient/.DS_Store create mode 100644 RNSwiftSocketIO/SocketIOClient/NSCharacterSet.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketEngine.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift rename RNSwiftSocketIO/SocketIOClient/{SocketFixUTF8.swift => SocketEnginePacketType.swift} (70%) mode change 100755 => 100644 create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketLogger.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketPacket.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/SocketParsable.swift delete mode 100755 RNSwiftSocketIO/SocketIOClient/SocketParser.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SocketTypes.swift create mode 100644 RNSwiftSocketIO/SocketIOClient/String.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift mode change 100755 => 100644 RNSwiftSocketIO/SocketIOClient/WebSocket.swift diff --git a/RNSwiftSocketIO/Socket.swift b/RNSwiftSocketIO/Socket.swift index 653c990..3e9b2d9 100644 --- a/RNSwiftSocketIO/Socket.swift +++ b/RNSwiftSocketIO/Socket.swift @@ -12,7 +12,7 @@ import Foundation class SocketIO: NSObject { var socket: SocketIOClient! - var connectionSocket: String! + var connectionSocket: NSURL! var bridge: RCTBridge! /** @@ -28,7 +28,7 @@ class SocketIO: NSObject { */ @objc func initialise(connection: String, config: NSDictionary) -> Void { - connectionSocket = connection + connectionSocket = NSURL(string: connection); // Connect to socket with config self.socket = SocketIOClient( @@ -44,8 +44,8 @@ class SocketIO: NSObject { * Manually join the namespace */ - @objc func joinNamespace() { - self.socket.joinNamespace(); + @objc func joinNamespace(namespace: String) -> Void { + self.socket.joinNamespace(namespace: namespace); } /** @@ -112,7 +112,7 @@ class SocketIO: NSObject { } // Disconnect from socket - @objc func close(fast: Bool) -> Void { - self.socket.close(fast: fast) + @objc func disconnect() -> Void { + self.socket.disconnect() } } diff --git a/RNSwiftSocketIO/SocketIOClient/.DS_Store b/RNSwiftSocketIO/SocketIOClient/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0) { + self.client = client + self.url = url + + for option in options { + switch option { + case let .ConnectParams(params): + connectParams = params + case let .Cookies(cookies): + self.cookies = cookies + case let .DoubleEncodeUTF8(encode): + doubleEncodeUTF8 = encode + case let .ExtraHeaders(headers): + extraHeaders = headers + case let .SessionDelegate(delegate): + sessionDelegate = delegate + case let .ForcePolling(force): + forcePolling = force + case let .ForceWebsockets(force): + forceWebsockets = force + case let .Path(path): + socketPath = path + case let .VoipEnabled(enable): + voipEnabled = enable + case let .Secure(secure): + self.secure = secure + case let .SelfSigned(selfSigned): + self.selfSigned = selfSigned + default: + continue } } + + super.init() + + (urlPolling, urlWebSocket) = createURLs() } - - public init(client: SocketEngineClient, sessionDelegate: NSURLSessionDelegate?) { - self.client = client - self.session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), - delegate: sessionDelegate, delegateQueue: workQueue) - } - - public convenience init(client: SocketEngineClient, opts: NSDictionary?) { - self.init(client: client, sessionDelegate: opts?["sessionDelegate"] as? NSURLSessionDelegate) - forceWebsockets = opts?["forceWebsockets"] as? Bool ?? false - forcePolling = opts?["forcePolling"] as? Bool ?? false - cookies = opts?["cookies"] as? [NSHTTPCookie] - socketPath = opts?["path"] as? String ?? "" - extraHeaders = opts?["extraHeaders"] as? [String: String] + + public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { + self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) } - + deinit { - Logger.log("Engine is being deinit", type: logType) + DefaultSocketLogger.Logger.log("Engine is being released", type: logType) + closed = true + stopPolling() } - private func checkIfMessageIsBase64Binary(var message: String) { - if message.hasPrefix("b4") { - // binary in base64 string - message.removeRange(Range(start: message.startIndex, - end: message.startIndex.advancedBy(2))) - - if let data = NSData(base64EncodedString: message, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - client?.parseBinaryData(data) + private func checkAndHandleEngineError(msg: String) { + guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false) else { return } + + do { + if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, options: .MutableContainers) as? NSDictionary { + guard let error = dict["message"] as? String else { return } + + /* + 0: Unknown transport + 1: Unknown sid + 2: Bad handshake request + 3: Bad request + */ + didError(error) } + } catch { + didError("Got unknown error from server \(msg)") } } - public func close(fast fast: Bool) { - Logger.log("Engine is being closed. Fast: %@", type: logType, args: fast) - - pingTimer?.invalidate() - closed = true - - ws?.disconnect() + private func checkIfMessageIsBase64Binary(message: String) -> Bool { + if message.hasPrefix("b4") { + // binary in base64 string + let noPrefix = message[message.startIndex.advancedBy(2).. (NSData?, String?) { - if websocket { - var byteArray = [UInt8](count: 1, repeatedValue: 0x0) - byteArray[0] = 4 - let mutData = NSMutableData(bytes: &byteArray, length: 1) - - mutData.appendData(data) - - return (mutData, nil) - } else { - var str = "b4" - str += data.base64EncodedStringWithOptions( - NSDataBase64EncodingOptions.Encoding64CharacterLineLength) - - return (nil, str) + + /// Starts the connection to the server + public func connect() { + if connected { + DefaultSocketLogger.Logger.error("Engine tried opening while connected. Assuming this was a reconnect", type: logType) + disconnect("reconnect") + } + + DefaultSocketLogger.Logger.log("Starting engine", type: logType) + DefaultSocketLogger.Logger.log("Handshaking", type: logType) + + resetEngine() + + if forceWebsockets { + polling = false + websocket = true + createWebsocketAndConnect() + return + } + + let reqPolling = NSMutableURLRequest(URL: urlPolling) + + if cookies != nil { + let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + reqPolling.allHTTPHeaderFields = headers + } + + if let extraHeaders = extraHeaders { + for (headerName, value) in extraHeaders { + reqPolling.setValue(value, forHTTPHeaderField: headerName) + } + } + + dispatch_async(emitQueue) { + self.doLongPoll(reqPolling) } } - private func createURLs(params: [String: AnyObject]?) -> (String?, String?) { + private func createURLs() -> (NSURL, NSURL) { if client == nil { - return (nil, nil) + return (NSURL(), NSURL()) } - let path = socketPath == "" ? "/socket.io" : socketPath - - let url = "\(client!.socketURL)\(path)/?transport=" - var urlPolling: String - var urlWebSocket: String + let urlPolling = NSURLComponents(string: url.absoluteString)! + let urlWebSocket = NSURLComponents(string: url.absoluteString)! + var queryString = "" + + urlWebSocket.path = socketPath + urlPolling.path = socketPath - if client!.secure { - urlPolling = "https://" + url + "polling" - urlWebSocket = "wss://" + url + "websocket" + if secure { + urlPolling.scheme = "https" + urlWebSocket.scheme = "wss" } else { - urlPolling = "http://" + url + "polling" - urlWebSocket = "ws://" + url + "websocket" - } - - if params != nil { - for (key, value) in params! { - let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( - allowedCharacterSet)! - urlPolling += "&\(keyEsc)=" - urlWebSocket += "&\(keyEsc)=" - - if value is String { - let valueEsc = (value as! String).stringByAddingPercentEncodingWithAllowedCharacters( - allowedCharacterSet)! - urlPolling += "\(valueEsc)" - urlWebSocket += "\(valueEsc)" - } else { - urlPolling += "\(value)" - urlWebSocket += "\(value)" - } + urlPolling.scheme = "http" + urlWebSocket.scheme = "ws" + } + + if connectParams != nil { + for (key, value) in connectParams! { + let keyEsc = key.urlEncode()! + let valueEsc = "\(value)".urlEncode()! + + queryString += "&\(keyEsc)=\(valueEsc)" } } - return (urlPolling, urlWebSocket) + urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString + urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString + + return (urlPolling.URL!, urlWebSocket.URL!) } - private func createWebsocket(andConnect connect: Bool) { - let wsUrl = urlWebSocket! + (sid == "" ? "" : "&sid=\(sid)") - - ws = WebSocket(url: NSURL(string: wsUrl)!, - cookies: cookies) + private func createWebsocketAndConnect() { + ws = WebSocket(url: urlWebSocketWithSid) + if cookies != nil { + let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + for (key, value) in headers { + ws?.headers[key] = value + } + } + if extraHeaders != nil { for (headerName, value) in extraHeaders! { ws?.headers[headerName] = value } } - + ws?.queue = handleQueue + ws?.voipEnabled = voipEnabled ws?.delegate = self + ws?.selfSignedSSL = selfSigned - if connect { - ws?.connect() + ws?.connect() + } + + public func didError(error: String) { + DefaultSocketLogger.Logger.error(error, type: logType) + client?.engineDidError(error) + disconnect(error) + } + + public func disconnect(reason: String) { + func postSendClose(data: NSData?, _ res: NSURLResponse?, _ err: NSError?) { + sid = "" + closed = true + invalidated = true + connected = false + + ws?.disconnect() + stopPolling() + client?.engineDidClose(reason) + } + + DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType) + + if closed { + client?.engineDidClose(reason) + return + } + + if websocket { + sendWebSocketMessage("", withType: .Close, withData: []) + postSendClose(nil, nil, nil) + } else { + // We need to take special care when we're polling that we send it ASAP + // Also make sure we're on the emitQueue since we're touching postWait + dispatch_sync(emitQueue) { + self.postWait.append(String(SocketEnginePacketType.Close.rawValue)) + let req = self.createRequestForPostWithPostWait() + self.doRequest(req, withCallback: postSendClose) + } } } - private func doFastUpgrade() { + public func doFastUpgrade() { if waitingForPoll { - Logger.error("Outstanding poll when switched to WebSockets," + + DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," + "we'll probably disconnect soon. You should report this.", type: logType) } - sendWebSocketMessage("", withType: PacketType.Upgrade, datas: nil) + sendWebSocketMessage("", withType: .Upgrade, withData: []) websocket = true polling = false fastUpgrade = false @@ -222,163 +315,40 @@ public final class SocketEngine: NSObject, WebSocketDelegate { flushProbeWait() } - private func doPoll() { - if websocket || waitingForPoll || !connected { - return - } - - waitingForPoll = true - let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)&b64=1")!) - - if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) - req.allHTTPHeaderFields = headers - } - - if extraHeaders != nil { - for (headerName, value) in extraHeaders! { - req.setValue(value, forHTTPHeaderField: headerName) - } - } - - doRequest(req) - } - - private func doRequest(req: NSMutableURLRequest) { - if !polling { - return - } - - req.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData - - Logger.log("Doing polling request", type: logType) - - session.dataTaskWithRequest(req) {[weak self] data, res, err in - if let this = self { - if err != nil || data == nil { - if this.polling { - this.handlePollingFailed(err?.localizedDescription ?? "Error") - } else { - Logger.error(err?.localizedDescription ?? "Error", type: this.logType) - } - return - } - - Logger.log("Got polling response", type: this.logType) - - if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) as? String { - dispatch_async(this.parseQueue) {[weak this] in - this?.parsePollingMessage(str) - } - } - - this.waitingForPoll = false - - if this.fastUpgrade { - this.doFastUpgrade() - } else if !this.closed && this.polling { - this.doPoll() - } - }}.resume() - } - private func flushProbeWait() { - Logger.log("Flushing probe wait", type: logType) + DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType) - dispatch_async(emitQueue) {[weak self] in - if let this = self { - for waiter in this.probeWait { - this.write(waiter.msg, withType: waiter.type, withData: waiter.data) - } - - this.probeWait.removeAll(keepCapacity: false) - - if this.postWait.count != 0 { - this.flushWaitingForPostToWebSocket() - } + dispatch_async(emitQueue) { + 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 postWait.count == 0 || !connected { - return - } else if websocket { - flushWaitingForPostToWebSocket() - return - } - - var postStr = "" - - for packet in postWait { - let len = packet.characters.count - - postStr += "\(len):\(packet)" - } - - postWait.removeAll(keepCapacity: false) - - let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)")!) - - if let cookies = cookies { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies) - req.allHTTPHeaderFields = headers - } - - req.HTTPMethod = "POST" - req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - - req.HTTPBody = postData - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - - waitingForPost = true - - Logger.log("POSTing: %@", type: logType, args: postStr) - - session.dataTaskWithRequest(req) {[weak self] data, res, err in - if let this = self { - if err != nil && this.polling { - this.handlePollingFailed(err?.localizedDescription ?? "Error") - return - } else if err != nil { - NSLog(err?.localizedDescription ?? "Error") - return - } - - this.waitingForPost = false - - dispatch_async(this.emitQueue) {[weak this] in - if !(this?.fastUpgrade ?? true) { - this?.flushWaitingForPost() - this?.doPoll() - } - } - }}.resume() - } - + // We had packets waiting for send when we upgraded // Send them raw - private func flushWaitingForPostToWebSocket() { - guard let ws = self.ws else {return} + public func flushWaitingForPostToWebSocket() { + guard let ws = self.ws else { return } for msg in postWait { - ws.writeString(msg) + ws.writeString(fixDoubleUTF8(msg)) } - + postWait.removeAll(keepCapacity: true) } - private func handleClose() { - if let client = client where polling == true { - client.engineDidClose("Disconnect") - } + private func handleClose(reason: String) { + client?.engineDidClose(reason) } private func handleMessage(message: String) { - client?.parseSocketMessage(message) + client?.parseEngineMessage(message) } private func handleNOOP() { @@ -395,31 +365,32 @@ public final class SocketEngine: NSObject, WebSocketDelegate { self.sid = sid connected = true - + if let upgrades = json?["upgrades"] as? [String] { - upgradeWs = upgrades.filter {$0 == "websocket"}.count != 0 + upgradeWs = upgrades.contains("websocket") } else { upgradeWs = false } - + if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { self.pingInterval = pingInterval / 1000.0 self.pingTimeout = pingTimeout / 1000.0 } - + if !forcePolling && !forceWebsockets && upgradeWs { - createWebsocket(andConnect: true) + createWebsocketAndConnect() } + + sendPing() + + if !forceWebsockets { + doPoll() + } + + client?.engineDidOpen?("Connect") } } catch { - Logger.error("Error parsing open packet", type: logType) - return - } - - startPingTimer() - - if !forceWebsockets { - doPoll() + didError("Error parsing open packet") } } @@ -431,233 +402,119 @@ public final class SocketEngine: NSObject, WebSocketDelegate { upgradeTransport() } } - - // A poll failed, tell the client about it - private func handlePollingFailed(reason: String) { - connected = false - ws?.disconnect() - pingTimer?.invalidate() - waitingForPoll = false - waitingForPost = false - - if !closed { - client?.didError(reason) - client?.engineDidClose(reason) - } + + public func parseEngineData(data: NSData) { + DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data) + client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) } - public func open(opts: [String: AnyObject]? = nil) { - if connected { - Logger.error("Tried to open while connected", type: logType) - client?.didError("Tried to open while connected") - - return - } - - Logger.log("Starting engine", type: logType) - Logger.log("Handshaking", type: logType) - - closed = false - - (urlPolling, urlWebSocket) = createURLs(opts) - - if forceWebsockets { - polling = false - websocket = true - createWebsocket(andConnect: true) - return - } - - let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&b64=1")!) - - if cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) - reqPolling.allHTTPHeaderFields = headers - } - - if let extraHeaders = extraHeaders { - for (headerName, value) in extraHeaders { - reqPolling.setValue(value, forHTTPHeaderField: headerName) - } - } + public func parseEngineMessage(message: String, fromPolling: Bool) { + DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message) - doRequest(reqPolling) - } + let reader = SocketStringReader(message: message) + let fixedString: String - private func parsePollingMessage(str: String) { - guard str.characters.count != 1 else { - return - } - - var reader = SocketStringReader(message: str) - - while reader.hasNext { - if let n = Int(reader.readUntilStringOccurence(":")) { - let str = reader.read(n) - - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } - } else { - dispatch_async(handleQueue) { - self.parseEngineMessage(str, fromPolling: true) - } - break + guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else { + if !checkIfMessageIsBase64Binary(message) { + checkAndHandleEngineError(message) } + + return } - } - - private func parseEngineData(data: NSData) { - Logger.log("Got binary data: %@", type: "SocketEngine", args: data) - client?.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - } - private func parseEngineMessage(var message: String, fromPolling: Bool) { - Logger.log("Got message: %@", type: logType, args: message) - - if fromPolling { - fixDoubleUTF8(&message) + if fromPolling && type != .Noop && doubleEncodeUTF8 { + fixedString = fixDoubleUTF8(message) + } else { + fixedString = message } - let type = PacketType(str: (message["^(\\d)"].groups()?[1]) ?? "") ?? { - self.checkIfMessageIsBase64Binary(message) - return .Noop - }() - switch type { - case PacketType.Message: - message.removeAtIndex(message.startIndex) - handleMessage(message) - case PacketType.Noop: + case .Message: + handleMessage(fixedString[fixedString.startIndex.successor().. pongsMissedMax { - pingTimer?.invalidate() client?.engineDidClose("Ping timeout") return } - - ++pongsMissed - write("", withType: PacketType.Ping, withData: nil) - } - - /// Send polling message. - /// Only call on emitQueue - private func sendPollMessage(var msg: String, withType type: PacketType, - datas:[NSData]? = nil) { - Logger.log("Sending poll: %@ as type: %@", type: logType, args: msg, type.rawValue) - - doubleEncodeUTF8(&msg) - let strMsg = "\(type.rawValue)\(msg)" - - postWait.append(strMsg) - - for data in datas ?? [] { - let (_, b64Data) = createBinaryDataForSend(data) - - postWait.append(b64Data!) - } - - if !waitingForPost { - flushWaitingForPost() - } - } - - /// Send message on WebSockets - /// Only call on emitQueue - private func sendWebSocketMessage(str: String, withType type: PacketType, - datas:[NSData]? = nil) { - Logger.log("Sending ws: %@ as type: %@", type: logType, args: str, type.rawValue) - - ws?.writeString("\(type.rawValue)\(str)") - - for data in datas ?? [] { - let (data, _) = createBinaryDataForSend(data) - if data != nil { - ws?.writeData(data!) - } - } - } - - // Starts the ping timer - private func startPingTimer() { + if let pingInterval = pingInterval { - pingTimer?.invalidate() - pingTimer = nil - - dispatch_async(dispatch_get_main_queue()) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(pingInterval, target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) + pongsMissed += 1 + write("", withType: .Ping, withData: []) + + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(pingInterval * Double(NSEC_PER_SEC))) + dispatch_after(time, dispatch_get_main_queue()) {[weak self] in + self?.sendPing() } } } - - func stopPolling() { - session.invalidateAndCancel() - } - + + // Moves from long-polling to websockets private func upgradeTransport() { - if websocketConnected { - Logger.log("Upgrading transport to WebSockets", type: logType) + if ws?.isConnected ?? false { + DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType) fastUpgrade = true - sendPollMessage("", withType: PacketType.Noop) + sendPollMessage("", withType: .Noop, withData: []) // After this point, we should not send anymore polling messages } } - /** - Write a message, independent of transport. - */ - public func write(msg: String, withType type: PacketType, withData data: [NSData]?) { + /// Write a message, independent of transport. + public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) { dispatch_async(emitQueue) { - if self.connected { - if self.websocket { - Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, - data == nil ? false : true) - self.sendWebSocketMessage(msg, withType: type, datas: data) - } else { - Logger.log("Writing poll: %@ has data: %@", type: self.logType, args: msg, - data == nil ? false : true) - self.sendPollMessage(msg, withType: type, datas: data) - } + guard self.connected else { return } + + if self.websocket { + DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@", + type: self.logType, args: msg, data.count != 0) + self.sendWebSocketMessage(msg, withType: type, withData: data) + } else if !self.probing { + DefaultSocketLogger.Logger.log("Writing poll: %@ has data: %@", + type: self.logType, args: msg, data.count != 0) + self.sendPollMessage(msg, withType: type, withData: data) + } else { + self.probeWait.append((msg, type, data)) } } } - - // Delagate methods - - public func websocketDidConnect(socket:WebSocket) { - websocketConnected = true - + + // Delegate methods + public func websocketDidConnect(socket: WebSocket) { if !forceWebsockets { probing = true probeWebSocket() @@ -667,25 +524,23 @@ public final class SocketEngine: NSObject, WebSocketDelegate { polling = false } } - + public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { - websocketConnected = false probing = false - + if closed { client?.engineDidClose("Disconnect") return } - + if websocket { - pingTimer?.invalidate() connected = false websocket = false - + let reason = error?.localizedDescription ?? "Socket Disconnected" - + if error != nil { - client?.didError(reason) + didError(reason) } client?.engineDidClose(reason) @@ -693,12 +548,4 @@ public final class SocketEngine: NSObject, WebSocketDelegate { flushProbeWait() } } - - public func websocketDidReceiveMessage(socket: WebSocket, text: String) { - parseEngineMessage(text, fromPolling: false) - } - - public func websocketDidReceiveData(socket: WebSocket, data: NSData) { - parseEngineData(data) - } } diff --git a/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift b/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift old mode 100755 new mode 100644 index bc0f4ae..a1db7f6 --- a/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift @@ -25,12 +25,10 @@ import Foundation -@objc public protocol SocketEngineClient { - var socketURL: String {get} - var secure: Bool {get} - - func didError(reason: AnyObject) +@objc public protocol SocketEngineClient { + func engineDidError(reason: String) func engineDidClose(reason: String) - func parseSocketMessage(msg: String) - func parseBinaryData(data: NSData) + optional func engineDidOpen(reason: String) + func parseEngineMessage(msg: String) + func parseEngineBinaryData(data: NSData) } diff --git a/RNSwiftSocketIO/SocketIOClient/SocketFixUTF8.swift b/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift old mode 100755 new mode 100644 similarity index 70% rename from RNSwiftSocketIO/SocketIOClient/SocketFixUTF8.swift rename to RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift index 0cffd86..592d79b --- a/RNSwiftSocketIO/SocketIOClient/SocketFixUTF8.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketEnginePacketType.swift @@ -1,8 +1,8 @@ // -// SocketFixUTF8.swift +// SocketEnginePacketType.swift // Socket.IO-Client-Swift // -// Created by Erik Little on 3/16/15. +// Created by Erik Little on 10/7/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 @@ -25,14 +25,6 @@ import Foundation -func fixDoubleUTF8(inout name: String) { - let utf8 = name.dataUsingEncoding(NSISOLatin1StringEncoding)! - let latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding)! - name = latin1 as String -} - -func doubleEncodeUTF8(inout str: String) { - let latin1 = str.dataUsingEncoding(NSUTF8StringEncoding)! - let utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding)! - str = utf8 as String -} +@objc public enum SocketEnginePacketType : Int { + case Open, Close, Ping, Pong, Message, Upgrade, Noop +} \ No newline at end of file diff --git a/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift b/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift new file mode 100644 index 0000000..c419e51 --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/SocketEnginePollable.swift @@ -0,0 +1,236 @@ +// +// SocketEnginePollable.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 1/15/16. +// +// 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 + +/// Protocol that is used to implement socket.io polling support +public protocol SocketEnginePollable : SocketEngineSpec { + var invalidated: Bool { get } + /// Holds strings waiting to be sent over polling. + /// You shouldn't need to mess with this. + var postWait: [String] { get set } + var session: NSURLSession? { get } + /// Because socket.io doesn't let you send two polling request at the same time + /// we have to keep track if there's an outstanding poll + var waitingForPoll: Bool { get set } + /// Because socket.io doesn't let you send two post request at the same time + /// we have to keep track if there's an outstanding post + var waitingForPost: Bool { get set } + + func doPoll() + func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) + func stopPolling() +} + +// Default polling methods +extension SocketEnginePollable { + private func addHeaders(req: NSMutableURLRequest) { + if cookies != nil { + let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) + req.allHTTPHeaderFields = headers + } + + if extraHeaders != nil { + for (headerName, value) in extraHeaders! { + req.setValue(value, forHTTPHeaderField: headerName) + } + } + } + + func createRequestForPostWithPostWait() -> NSURLRequest { + var postStr = "" + + for packet in postWait { + let len = packet.characters.count + + postStr += "\(len):\(packet)" + } + + DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) + + postWait.removeAll(keepCapacity: false) + + let req = NSMutableURLRequest(URL: urlPollingWithSid) + + addHeaders(req) + + 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") + + return req + } + + public func doPoll() { + if websocket || waitingForPoll || !connected || closed { + return + } + + waitingForPoll = true + let req = NSMutableURLRequest(URL: urlPollingWithSid) + + addHeaders(req) + doLongPoll(req) + } + + func doRequest(req: NSURLRequest, withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { + if !polling || closed || invalidated || fastUpgrade { + DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: "SocketEnginePolling") + return + } + + DefaultSocketLogger.Logger.log("Doing polling request", type: "SocketEnginePolling") + + session?.dataTaskWithRequest(req, completionHandler: callback).resume() + } + + func doLongPoll(req: NSURLRequest) { + doRequest(req) {[weak self] data, res, err in + guard let this = self where this.polling else { return } + + if err != nil || data == nil { + DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") + + if this.polling { + this.didError(err?.localizedDescription ?? "Error") + } + + return + } + + DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") + + if let str = String(data: data!, encoding: NSUTF8StringEncoding) { + dispatch_async(this.parseQueue) { + this.parsePollingMessage(str) + } + } + + this.waitingForPoll = false + + if this.fastUpgrade { + this.doFastUpgrade() + } else if !this.closed && this.polling { + this.doPoll() + } + } + } + + private func flushWaitingForPost() { + if postWait.count == 0 || !connected { + return + } else if websocket { + flushWaitingForPostToWebSocket() + return + } + + let req = createRequestForPostWithPostWait() + + waitingForPost = true + + DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") + + doRequest(req) {[weak self] data, res, err in + guard let this = self else { return } + + if err != nil { + DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") + + if this.polling { + this.didError(err?.localizedDescription ?? "Error") + } + + return + } + + this.waitingForPost = false + + dispatch_async(this.emitQueue) { + if !this.fastUpgrade { + this.flushWaitingForPost() + this.doPoll() + } + } + } + } + + func parsePollingMessage(str: String) { + guard str.characters.count != 1 else { return } + + var reader = SocketStringReader(message: str) + + while reader.hasNext { + if let n = Int(reader.readUntilStringOccurence(":")) { + let str = reader.read(n) + + dispatch_async(handleQueue) { + self.parseEngineMessage(str, fromPolling: true) + } + } else { + dispatch_async(handleQueue) { + self.parseEngineMessage(str, fromPolling: true) + } + break + } + } + } + + /// Send polling message. + /// Only call on emitQueue + public func sendPollMessage(message: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { + DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) + let fixedMessage: String + + if doubleEncodeUTF8 { + fixedMessage = doubleEncodeUTF8(message) + } else { + fixedMessage = message + } + + let strMsg = "\(type.rawValue)\(fixedMessage)" + + postWait.append(strMsg) + + for data in datas { + if case let .Right(bin) = createBinaryDataForSend(data) { + postWait.append(bin) + } + } + + if !waitingForPost { + flushWaitingForPost() + } + } + + public func stopPolling() { + waitingForPoll = false + waitingForPost = false + session?.finishTasksAndInvalidate() + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift b/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift new file mode 100644 index 0000000..7fdd779 --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/SocketEngineSpec.swift @@ -0,0 +1,115 @@ +// +// SocketEngineSpec.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 10/7/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 public protocol SocketEngineSpec { + weak var client: SocketEngineClient? { get set } + var closed: Bool { get } + var connected: Bool { get } + var connectParams: [String: AnyObject]? { get set } + var doubleEncodeUTF8: Bool { get } + var cookies: [NSHTTPCookie]? { get } + var extraHeaders: [String: String]? { get } + var fastUpgrade: Bool { get } + var forcePolling: Bool { get } + var forceWebsockets: Bool { get } + var parseQueue: dispatch_queue_t! { get } + var polling: Bool { get } + var probing: Bool { get } + var emitQueue: dispatch_queue_t! { get } + var handleQueue: dispatch_queue_t! { get } + var sid: String { get } + var socketPath: String { get } + var urlPolling: NSURL { get } + var urlWebSocket: NSURL { get } + var websocket: Bool { get } + var ws: WebSocket? { get } + + init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) + + func connect() + func didError(error: String) + func disconnect(reason: String) + func doFastUpgrade() + func flushWaitingForPostToWebSocket() + func parseEngineData(data: NSData) + func parseEngineMessage(message: String, fromPolling: Bool) + func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) +} + +extension SocketEngineSpec { + var urlPollingWithSid: NSURL { + let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)! + com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" + + return com.URL! + } + + var urlWebSocketWithSid: NSURL { + let com = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! + com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") + + return com.URL! + } + + func createBinaryDataForSend(data: NSData) -> Either { + if websocket { + var byteArray = [UInt8](count: 1, repeatedValue: 0x4) + let mutData = NSMutableData(bytes: &byteArray, length: 1) + + mutData.appendData(data) + + return .Left(mutData) + } else { + let str = "b4" + data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) + + return .Right(str) + } + } + + func doubleEncodeUTF8(string: String) -> String { + if let latin1 = string.dataUsingEncoding(NSUTF8StringEncoding), + utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding) { + return utf8 as String + } else { + return string + } + } + + func fixDoubleUTF8(string: String) -> String { + if let utf8 = string.dataUsingEncoding(NSISOLatin1StringEncoding), + latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding) { + return latin1 as String + } else { + return string + } + } + + /// Send an engine message (4) + func send(msg: String, withData datas: [NSData]) { + write(msg, withType: .Message, withData: datas) + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift b/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift new file mode 100644 index 0000000..e1b0ba8 --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/SocketEngineWebsocket.swift @@ -0,0 +1,62 @@ +// +// SocketEngineWebsocket.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 1/15/16. +// +// 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 + +/// Protocol that is used to implement socket.io WebSocket support +public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { + func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) +} + +// WebSocket methods +extension SocketEngineWebsocket { + func probeWebSocket() { + if ws?.isConnected ?? false { + sendWebSocketMessage("probe", withType: .Ping, withData: []) + } + } + + /// Send message on WebSockets + /// Only call on emitQueue + public func sendWebSocketMessage(str: String, withType type: SocketEnginePacketType, withData datas: [NSData]) { + DefaultSocketLogger.Logger.log("Sending ws: %@ as type: %@", type: "SocketEngine", args: str, type.rawValue) + + ws?.writeString("\(type.rawValue)\(str)") + + for data in datas { + if case let .Left(bin) = createBinaryDataForSend(data) { + ws?.writeData(bin) + } + } + } + + public func websocketDidReceiveMessage(socket: WebSocket, text: String) { + parseEngineMessage(text, fromPolling: false) + } + + public func websocketDidReceiveData(socket: WebSocket, data: NSData) { + parseEngineData(data) + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift b/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift old mode 100755 new mode 100644 index e518e4c..41774a9 --- a/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift @@ -24,23 +24,12 @@ import Foundation -private func emitAckCallback(socket: SocketIOClient, num: Int?) -> SocketAckEmitter? { - return num != nil ? SocketAckEmitter(socket: socket, ackNum: num!) : nil -} - struct SocketEventHandler { let event: String - let callback: NormalCallback let id: NSUUID + let callback: NormalCallback - init(event: String, id: NSUUID = NSUUID(), callback: NormalCallback) { - self.event = event - self.id = id - self.callback = callback - } - - func executeCallback(items: [AnyObject], withAck ack: Int? = nil, withAckType type: Int? = nil, - withSocket socket: SocketIOClient) { - self.callback(items, emitAckCallback(socket, num: ack)) + func executeCallback(items: [AnyObject], withAck ack: Int, withSocket socket: SocketIOClient) { + callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) } } diff --git a/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift b/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift old mode 100755 new mode 100644 index 3c716b9..02cda38 --- a/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift @@ -24,486 +24,434 @@ import Foundation -public final class SocketIOClient: NSObject, SocketEngineClient { - private let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL) - private let handleQueue: dispatch_queue_t! +public final class SocketIOClient : NSObject, SocketEngineClient, SocketParsable { + public let socketURL: NSURL - public let socketURL: String + public private(set) var engine: SocketEngineSpec? + public private(set) var status = SocketIOClientStatus.NotConnected { + didSet { + switch status { + case .Connected: + reconnecting = false + currentReconnectAttempt = 0 + default: + break + } + } + } - public private(set) var engine: SocketEngine? - public private(set) var secure = false - public private(set) var status = SocketIOClientStatus.NotConnected - + public var forceNew = false public var nsp = "/" - public var opts: [String: AnyObject]? + public var options: Set public var reconnects = true public var reconnectWait = 10 public var sid: String? { - return engine?.sid + return nsp + "#" + (engine?.sid ?? "") } - + + private let emitQueue = dispatch_queue_create("com.socketio.emitQueue", DISPATCH_QUEUE_SERIAL) private let logType = "SocketIOClient" - + private let parseQueue = dispatch_queue_create("com.socketio.parseQueue", DISPATCH_QUEUE_SERIAL) + private var anyHandler: ((SocketAnyEvent) -> Void)? private var currentReconnectAttempt = 0 - private var handlers = ContiguousArray() - private var connectParams: [String: AnyObject]? - private var reconnectTimer: NSTimer? - - private let reconnectAttempts: Int! + private var handlers = [SocketEventHandler]() private var ackHandlers = SocketAckManager() - private var currentAck = -1 + private var reconnecting = false + + private(set) var currentAck = -1 + private(set) var handleQueue = dispatch_get_main_queue() + private(set) var reconnectAttempts = -1 - var waitingData = [SocketPacket]() + var waitingPackets = [SocketPacket]() - /** - Create a new SocketIOClient. opts can be omitted - */ - public init(var socketURL: String, opts: [String: AnyObject]? = nil) { - if socketURL["https://"].matches().count != 0 { - self.secure = true - } - - socketURL = socketURL["http://"] ~= "" - socketURL = socketURL["https://"] ~= "" - + /// Type safe way to create a new SocketIOClient. opts can be omitted + public init(socketURL: NSURL, options: Set = []) { + self.options = options self.socketURL = socketURL - self.opts = opts - - if let connectParams = opts?["connectParams"] as? [String: AnyObject] { - self.connectParams = connectParams - } - if let logger = opts?["logger"] as? SocketLogger { - Logger = logger + if socketURL.absoluteString.hasPrefix("https://") { + self.options.insertIgnore(.Secure(true)) } - if let log = opts?["log"] as? Bool { - Logger.log = log - } - - if let nsp = opts?["nsp"] as? String { - self.nsp = nsp - } - - 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) + for option in options { + switch option { + case let .Reconnects(reconnects): + self.reconnects = reconnects + case let .ReconnectAttempts(attempts): + reconnectAttempts = attempts + case let .ReconnectWait(wait): + reconnectWait = abs(wait) + case let .Nsp(nsp): + self.nsp = nsp + case let .Log(log): + DefaultSocketLogger.Logger.log = log + case let .Logger(logger): + DefaultSocketLogger.Logger = logger + case let .HandleQueue(queue): + handleQueue = queue + case let .ForceNew(force): + forceNew = force + default: + continue + } } - if let handleQueue = opts?["handleQueue"] as? dispatch_queue_t { - self.handleQueue = handleQueue - } else { - self.handleQueue = dispatch_get_main_queue() - } + self.options.insertIgnore(.Path("/socket.io/")) super.init() } + /// Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. + /// If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` + public convenience init(socketURL: NSURL, options: NSDictionary?) { + self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? []) + } + deinit { - Logger.log("Client is being deinit", type: logType) - engine?.close(fast: true) + DefaultSocketLogger.Logger.log("Client is being released", type: logType) + engine?.disconnect("Client Deinit") } - - private func addEngine() -> SocketEngine { - Logger.log("Adding engine", type: logType) - let newEngine = SocketEngine(client: self, opts: opts) + private func addEngine() -> SocketEngineSpec { + DefaultSocketLogger.Logger.log("Adding engine", type: logType) - engine = newEngine - return newEngine - } - - private func clearReconnectTimer() { - reconnectTimer?.invalidate() - reconnectTimer = nil - } - - /** - Closes the socket. Only reopen the same socket if you know what you're doing. - Will turn off automatic reconnects. - Pass true to fast if you're closing from a background task - */ - public func close() { - Logger.log("Closing socket", type: logType) - - reconnects = false - didDisconnect("Closed") + engine = SocketEngine(client: self, url: socketURL, options: options) + + return engine! } - - /** - Connect to the server. - */ + + /// Connect to the server. public func connect() { connect(timeoutAfter: 0, withTimeoutHandler: nil) } - - /** - Connect to the server. If we aren't connected after timeoutAfter, call handler - */ - public func connect(timeoutAfter timeoutAfter: Int, - withTimeoutHandler handler: (() -> Void)?) { - assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") - - guard status != .Connected else { - return - } - if status == .Closed { - Logger.log("Warning! This socket was previously closed. This might be dangerous!", - type: logType) - } - - status = SocketIOClientStatus.Connecting - addEngine().open(connectParams) - - guard timeoutAfter != 0 else { - return - } - - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) - - dispatch_after(time, dispatch_get_main_queue()) { - if self.status != .Connected { - self.status = .Closed - self.engine?.close(fast: true) - - handler?() - } + /// Connect to the server. If we aren't connected after timeoutAfter, call handler + public func connect(timeoutAfter timeoutAfter: Int, withTimeoutHandler handler: (() -> Void)?) { + assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") + + guard status != .Connected else { + DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType) + return + } + + status = .Connecting + + if engine == nil || forceNew { + addEngine().connect() + } else { + engine?.connect() + } + + guard timeoutAfter != 0 else { return } + + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) + + dispatch_after(time, handleQueue) {[weak self] in + if let this = self where this.status != .Connected && this.status != .Disconnected { + this.status = .Disconnected + this.engine?.disconnect("Connect timeout") + + handler?() } + } } - + private func createOnAck(items: [AnyObject]) -> OnAckCallback { - return {[weak self, ack = ++currentAck] timeout, callback in + currentAck += 1 + + return {[weak self, ack = currentAck] timeout, callback in if let this = self { this.ackHandlers.addAck(ack, callback: callback) - - dispatch_async(this.emitQueue) { - this._emit(items, ack: ack) - } - + this._emit(items, ack: ack) + if timeout != 0 { let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) - - dispatch_after(time, dispatch_get_main_queue()) { + + dispatch_after(time, this.handleQueue) { this.ackHandlers.timeoutAck(ack) } } } } } - + func didConnect() { - Logger.log("Socket connected", type: logType) + DefaultSocketLogger.Logger.log("Socket connected", type: logType) status = .Connected - currentReconnectAttempt = 0 - clearReconnectTimer() - + // Don't handle as internal because something crazy could happen where // we disconnect before it's handled handleEvent("connect", data: [], isInternalMessage: false) } - + func didDisconnect(reason: String) { - guard status != .Closed else { - return - } - - Logger.log("Disconnected: %@", type: logType, args: reason) - - status = .Closed + guard status != .Disconnected else { return } + + DefaultSocketLogger.Logger.log("Disconnected: %@", type: logType, args: reason) + + status = .Disconnected reconnects = false - + // Make sure the engine is actually dead. - engine?.close(fast: true) + engine?.disconnect(reason) handleEvent("disconnect", data: [reason], isInternalMessage: true) } - - /// error - public func didError(reason: AnyObject) { - Logger.error("%@", type: logType, args: reason) - - handleEvent("error", data: reason as? [AnyObject] ?? [reason], - isInternalMessage: true) - } - - /** - Same as close - */ + + /// Disconnects the socket. Only reconnect the same socket if you know what you're doing. + /// Will turn off automatic reconnects. public func disconnect() { - close() + assert(status != .NotConnected, "Tried closing a NotConnected client") + + DefaultSocketLogger.Logger.log("Closing socket", type: logType) + + reconnects = false + didDisconnect("Disconnect") } - - /** - Send a message to the server - */ + + /// Send a message to the server public func emit(event: String, _ items: AnyObject...) { emit(event, withItems: items) } - - /** - Same as emit, but meant for Objective-C - */ + + /// Same as emit, but meant for Objective-C public func emit(event: String, withItems items: [AnyObject]) { guard status == .Connected else { + handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) return } - - dispatch_async(emitQueue) { - self._emit([event] + items) - } + + _emit([event] + items) } - - /** - Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add - an ack. - */ + + /// Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add + /// an ack. public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { return emitWithAck(event, withItems: items) } - - /** - Same as emitWithAck, but for Objective-C - */ + + /// Same as emitWithAck, but for Objective-C public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { return createOnAck([event] + items) } - + private func _emit(data: [AnyObject], ack: Int? = nil) { - guard status == .Connected else { - return - } - - let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false) - let str = packet.packetString - - Logger.log("Emitting: %@", type: logType, args: str) - - if packet.type == .BinaryEvent { - engine?.send(str, withData: packet.binary) - } else { - engine?.send(str, withData: nil) + dispatch_async(emitQueue) { + guard self.status == .Connected else { + self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) + return + } + + let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: self.nsp, ack: false) + let str = packet.packetString + + DefaultSocketLogger.Logger.log("Emitting: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } } - + // If the server wants to know that the client received data func emitAck(ack: Int, withItems items: [AnyObject]) { dispatch_async(emitQueue) { if self.status == .Connected { let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) let str = packet.packetString - - Logger.log("Emitting Ack: %@", type: self.logType, args: str) - - if packet.type == SocketPacket.PacketType.BinaryAck { - self.engine?.send(str, withData: packet.binary) - } else { - self.engine?.send(str, withData: nil) - } - + + DefaultSocketLogger.Logger.log("Emitting Ack: %@", type: self.logType, args: str) + + self.engine?.send(str, withData: packet.binary) } } } - + public func engineDidClose(reason: String) { - waitingData.removeAll() + waitingPackets.removeAll() - if status == .Closed || !reconnects { + if status != .Disconnected { + status = .NotConnected + } + + if status == .Disconnected || !reconnects { didDisconnect(reason) - } else if status != .Reconnecting { - status = .Reconnecting - handleEvent("reconnect", data: [reason], isInternalMessage: true) - tryReconnect() + } else if !reconnecting { + reconnecting = true + tryReconnectWithReason(reason) } } - + + /// error + public func engineDidError(reason: String) { + DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) + + handleEvent("error", data: [reason], isInternalMessage: true) + } + // Called when the socket gets an ack for something it sent - func handleAck(ack: Int, data: AnyObject?) { - Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") - - ackHandlers.executeAck(ack, - items: (data as? [AnyObject]) ?? (data != nil ? [data!] : [])) + func handleAck(ack: Int, data: [AnyObject]) { + guard status == .Connected else { return } + + DefaultSocketLogger.Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") + + ackHandlers.executeAck(ack, items: data) } - - /** - Causes an event to be handled. Only use if you know what you're doing. - */ - public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, - wantsAck ack: Int? = nil) { - guard status == .Connected || isInternalMessage else { - return - } - - Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") - - if anyHandler != nil { - dispatch_async(handleQueue) { - self.anyHandler?(SocketAnyEvent(event: event, items: data)) - } - } - - for handler in handlers where handler.event == event { - if let ack = ack { - dispatch_async(handleQueue) { - handler.executeCallback(data, withAck: ack, withSocket: self) - } - } else { - dispatch_async(handleQueue) { - handler.executeCallback(data, withAck: ack, withSocket: self) - } - } + + /// Causes an event to be handled. Only use if you know what you're doing. + public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int = -1) { + guard status == .Connected || isInternalMessage else { + return + } + + DefaultSocketLogger.Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") + + dispatch_async(handleQueue) { + self.anyHandler?(SocketAnyEvent(event: event, items: data)) + + for handler in self.handlers where handler.event == event { + handler.executeCallback(data, withAck: ack, withSocket: self) } + } } - - /** - Leaves nsp and goes back to / - */ + + /// Leaves nsp and goes back to / public func leaveNamespace() { if nsp != "/" { - engine?.send("1\(nsp)", withData: nil) + engine?.send("1\(nsp)", withData: []) nsp = "/" } } - - /** - Joins nsp if it is not / - */ - public func joinNamespace() { - Logger.log("Joining namespace", type: logType) - + + /// Joins namespace + public func joinNamespace(namespace: String) { + nsp = namespace + if nsp != "/" { - engine?.send("0\(nsp)", withData: nil) + DefaultSocketLogger.Logger.log("Joining namespace", type: logType) + engine?.send("0\(nsp)", withData: []) } } - - /** - Joins namespace / - */ - public func joinNamespace(namespace: String) { - self.nsp = namespace - joinNamespace() - } - - /** - Removes handler(s) - */ + + /// Removes handler(s) based on name public func off(event: String) { - Logger.log("Removing handler for event: %@", type: logType, args: event) - - handlers = ContiguousArray(handlers.filter { $0.event != event }) + DefaultSocketLogger.Logger.log("Removing handler for event: %@", type: logType, args: event) + + handlers = handlers.filter { $0.event != event } } - - /** - Adds a handler for an event. - */ - public func on(event: String, callback: NormalCallback) { - Logger.log("Adding handler for event: %@", type: logType, args: event) - - let handler = SocketEventHandler(event: event, callback: callback) + + /// Removes a handler with the specified UUID gotten from an `on` or `once` + public func off(id id: NSUUID) { + DefaultSocketLogger.Logger.log("Removing handler with id: %@", type: logType, args: id) + + handlers = handlers.filter { $0.id != id } + } + + /// Adds a handler for an event. + /// Returns: A unique id for the handler + public func on(event: String, callback: NormalCallback) -> NSUUID { + DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) + + let handler = SocketEventHandler(event: event, id: NSUUID(), callback: callback) handlers.append(handler) + + return handler.id } - - /** - Adds a single-use handler for an event. - */ - public func once(event: String, callback: NormalCallback) { - Logger.log("Adding once handler for event: %@", type: logType, args: event) - + + /// Adds a single-use handler for an event. + /// Returns: A unique id for the handler + public func once(event: String, callback: NormalCallback) -> NSUUID { + DefaultSocketLogger.Logger.log("Adding once handler for event: %@", type: logType, args: event) + let id = NSUUID() - + let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in - guard let this = self else {return} - this.handlers = ContiguousArray(this.handlers.filter {$0.id != id}) + guard let this = self else { return } + this.off(id: id) callback(data, ack) } handlers.append(handler) + + return handler.id } - - /** - Removes all handlers. - Can be used after disconnecting to break any potential remaining retain cycles. - */ - public func removeAllHandlers() { - handlers.removeAll(keepCapacity: false) - } - - /** - Adds a handler that will be called on every event. - */ + + /// Adds a handler that will be called on every event. public func onAny(handler: (SocketAnyEvent) -> Void) { anyHandler = handler } - - /** - Same as connect - */ - public func open() { - connect() - } - - public func parseSocketMessage(msg: String) { - dispatch_async(handleQueue) { - SocketParser.parseSocketMessage(msg, socket: self) + + public func parseEngineMessage(msg: String) { + DefaultSocketLogger.Logger.log("Should parse message: %@", type: "SocketIOClient", args: msg) + + dispatch_async(parseQueue) { + self.parseSocketMessage(msg) } } - - public func parseBinaryData(data: NSData) { - dispatch_async(handleQueue) { - SocketParser.parseBinaryData(data, socket: self) + + public func parseEngineBinaryData(data: NSData) { + dispatch_async(parseQueue) { + self.parseBinaryData(data) } } - - /** - Tries to reconnect to the server. - */ + + /// Tries to reconnect to the server. public func reconnect() { - engine?.stopPolling() - tryReconnect() + guard !reconnecting else { return } + + engine?.disconnect("manual reconnect") } - - private func tryReconnect() { - if reconnectTimer == nil { - Logger.log("Starting reconnect", type: logType) - - status = .Reconnecting + + /// Removes all handlers. + /// Can be used after disconnecting to break any potential remaining retain cycles. + public func removeAllHandlers() { + handlers.removeAll(keepCapacity: false) + } + + private func tryReconnectWithReason(reason: String) { + if reconnecting { + DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) + handleEvent("reconnect", data: [reason], isInternalMessage: true) - dispatch_async(dispatch_get_main_queue()) { - self.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self.reconnectWait), - target: self, selector: "_tryReconnect", userInfo: nil, repeats: true) - } + _tryReconnect() } } - @objc private func _tryReconnect() { - if status == .Connected { - clearReconnectTimer() - + private func _tryReconnect() { + if !reconnecting { return } - - + if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { - clearReconnectTimer() - didDisconnect("Reconnect Failed") - - return + return didDisconnect("Reconnect Failed") } - - Logger.log("Trying to reconnect", type: logType) + + DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], isInternalMessage: true) - - currentReconnectAttempt++ + + currentReconnectAttempt += 1 connect() + + let dispatchAfter = dispatch_time(DISPATCH_TIME_NOW, Int64(UInt64(reconnectWait) * NSEC_PER_SEC)) + + dispatch_after(dispatchAfter, dispatch_get_main_queue(), _tryReconnect) + } +} + +// Test extensions +extension SocketIOClient { + var testHandlers: [SocketEventHandler] { + return handlers + } + + func setTestable() { + status = .Connected + } + + func setTestEngine(engine: SocketEngineSpec?) { + self.engine = engine + } + + func emitTest(event: String, _ data: AnyObject...) { + self._emit([event] + data) } } diff --git a/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift b/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift new file mode 100644 index 0000000..93626f5 --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/SocketIOClientOption.swift @@ -0,0 +1,220 @@ +// +// SocketIOClientOption .swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 10/17/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 + +protocol ClientOption : CustomStringConvertible, Hashable { + func getSocketIOOptionValue() -> AnyObject +} + +public enum SocketIOClientOption : ClientOption { + case ConnectParams([String: AnyObject]) + case Cookies([NSHTTPCookie]) + case DoubleEncodeUTF8(Bool) + case ExtraHeaders([String: String]) + case ForceNew(Bool) + case ForcePolling(Bool) + case ForceWebsockets(Bool) + case HandleQueue(dispatch_queue_t) + case Log(Bool) + case Logger(SocketLogger) + case Nsp(String) + case Path(String) + case Reconnects(Bool) + case ReconnectAttempts(Int) + case ReconnectWait(Int) + case Secure(Bool) + case SelfSigned(Bool) + case SessionDelegate(NSURLSessionDelegate) + case VoipEnabled(Bool) + + public var description: String { + let description: String + + switch self { + case .ConnectParams: + description = "connectParams" + case .Cookies: + description = "cookies" + case .DoubleEncodeUTF8: + description = "doubleEncodeUTF8" + case .ExtraHeaders: + description = "extraHeaders" + case .ForceNew: + description = "forceNew" + case .ForcePolling: + description = "forcePolling" + case .ForceWebsockets: + description = "forceWebsockets" + case .HandleQueue: + description = "handleQueue" + case .Log: + description = "log" + case .Logger: + description = "logger" + case .Nsp: + description = "nsp" + case .Path: + description = "path" + case .Reconnects: + description = "reconnects" + case .ReconnectAttempts: + description = "reconnectAttempts" + case .ReconnectWait: + description = "reconnectWait" + case .Secure: + description = "secure" + case .SelfSigned: + description = "selfSigned" + case .SessionDelegate: + description = "sessionDelegate" + case .VoipEnabled: + description = "voipEnabled" + } + + return description + } + + public var hashValue: Int { + return description.hashValue + } + + func getSocketIOOptionValue() -> AnyObject { + let value: AnyObject + + switch self { + case let .ConnectParams(params): + value = params + case let .Cookies(cookies): + value = cookies + case let .DoubleEncodeUTF8(encode): + value = encode + case let .ExtraHeaders(headers): + value = headers + case let .ForceNew(force): + value = force + case let .ForcePolling(force): + value = force + case let .ForceWebsockets(force): + value = force + case let .HandleQueue(queue): + value = queue + case let .Log(log): + value = log + case let .Logger(logger): + value = logger + case let .Nsp(nsp): + value = nsp + case let .Path(path): + value = path + case let .Reconnects(reconnects): + value = reconnects + case let .ReconnectAttempts(attempts): + value = attempts + case let .ReconnectWait(wait): + value = wait + case let .Secure(secure): + value = secure + case let .SelfSigned(signed): + value = signed + case let .SessionDelegate(delegate): + value = delegate + case let .VoipEnabled(enabled): + value = enabled + } + + return value + } +} + +public func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { + return lhs.description == rhs.description +} + +extension Set where Element : ClientOption { + mutating func insertIgnore(element: Element) { + if !contains(element) { + insert(element) + } + } +} + +extension NSDictionary { + private static func keyValueToSocketIOClientOption(key: String, value: AnyObject) -> SocketIOClientOption? { + switch (key, value) { + case let ("connectParams", params as [String: AnyObject]): + return .ConnectParams(params) + case let ("cookies", cookies as [NSHTTPCookie]): + return .Cookies(cookies) + case let ("doubleEncodeUTF8", encode as Bool): + return .DoubleEncodeUTF8(encode) + case let ("extraHeaders", headers as [String: String]): + return .ExtraHeaders(headers) + case let ("forceNew", force as Bool): + return .ForceNew(force) + case let ("forcePolling", force as Bool): + return .ForcePolling(force) + case let ("forceWebsockets", force as Bool): + return .ForceWebsockets(force) + case let ("handleQueue", queue as dispatch_queue_t): + return .HandleQueue(queue) + case let ("log", log as Bool): + return .Log(log) + case let ("logger", logger as SocketLogger): + return .Logger(logger) + case let ("nsp", nsp as String): + return .Nsp(nsp) + case let ("path", path as String): + return .Path(path) + case let ("reconnects", reconnects as Bool): + return .Reconnects(reconnects) + case let ("reconnectAttempts", attempts as Int): + return .ReconnectAttempts(attempts) + case let ("reconnectWait", wait as Int): + return .ReconnectWait(wait) + case let ("secure", secure as Bool): + return .Secure(secure) + case let ("selfSigned", selfSigned as Bool): + return .SelfSigned(selfSigned) + case let ("sessionDelegate", delegate as NSURLSessionDelegate): + return .SessionDelegate(delegate) + case let ("voipEnabled", enable as Bool): + return .VoipEnabled(enable) + default: + return nil + } + } + + func toSocketOptionsSet() -> Set { + var options = Set() + + for (rawKey, value) in self { + if let key = rawKey as? String, opt = NSDictionary.keyValueToSocketIOClientOption(key, value: value) { + options.insertIgnore(opt) + } + } + + return options + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift b/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift new file mode 100644 index 0000000..8b33cf9 --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/SocketIOClientSpec.swift @@ -0,0 +1,43 @@ +// +// SocketIOClientSpec.swift +// Socket.IO-Client-Swift +// +// Created by Erik Little on 1/3/16. +// +// 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. + +protocol SocketIOClientSpec : class { + var nsp: String { get set } + var waitingPackets: [SocketPacket] { get set } + + func didConnect() + func didDisconnect(reason: String) + func didError(reason: String) + func handleAck(ack: Int, data: [AnyObject]) + func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, withAck ack: Int) + func joinNamespace(namespace: String) +} + +extension SocketIOClientSpec { + func didError(reason: String) { + DefaultSocketLogger.Logger.error("%@", type: "SocketIOClient", args: reason) + + handleEvent("error", data: [reason], isInternalMessage: true, withAck: -1) + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift b/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift old mode 100755 new mode 100644 index db9f959..0a34c2f --- a/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift @@ -24,21 +24,9 @@ import Foundation -@objc public enum SocketIOClientStatus: Int, CustomStringConvertible { - public var description: String { - switch self { - case NotConnected: - return "Not Connected" - case Closed: - return "Closed" - case Connecting: - return "Connecting" - case Connected: - return "Connected" - case Reconnecting: - return "Reconnecting" - } - } - - case NotConnected, Closed, Connecting, Connected, Reconnecting -} \ No newline at end of file +/// **NotConnected**: initial state +/// +/// **Disconnected**: connected before +@objc public enum SocketIOClientStatus : Int { + case NotConnected, Disconnected, Connecting, Connected +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift b/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift old mode 100755 new mode 100644 index 6c62aa9..bff9d4e --- a/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketLogger.swift @@ -24,11 +24,9 @@ import Foundation -var Logger: SocketLogger = DefaultSocketLogger() - -public protocol SocketLogger { +public protocol SocketLogger : class { /// Whether to log or not - var log: Bool {get set} + var log: Bool { get set } /// Normal log messages func log(message: String, type: String, args: AnyObject...) @@ -39,7 +37,7 @@ public protocol SocketLogger { public extension SocketLogger { func log(message: String, type: String, args: AnyObject...) { - abstractLog("Log", message: message, type: type, args: args) + abstractLog("LOG", message: message, type: type, args: args) } func error(message: String, type: String, args: AnyObject...) { @@ -49,13 +47,15 @@ public extension SocketLogger { private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { guard log else { return } - let newArgs = args.map {arg -> CVarArgType in String(arg)} + let newArgs = args.map({arg -> CVarArgType in String(arg)}) let replaced = String(format: message, arguments: newArgs) NSLog("%@ %@: %@", logType, type, replaced) } } -struct DefaultSocketLogger: SocketLogger { +class DefaultSocketLogger : SocketLogger { + static var Logger: SocketLogger = DefaultSocketLogger() + var log = false } diff --git a/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift b/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift old mode 100755 new mode 100644 index 5608d7d..52de38a --- a/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketPacket.swift @@ -21,14 +21,14 @@ // 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 struct SocketPacket { private let placeholders: Int - private var currentPlace = 0 - - private static let logType = "SocketPacket" + + private static let logType = "SocketPacket" let nsp: String let id: Int @@ -36,28 +36,13 @@ struct SocketPacket { enum PacketType: Int { case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck - - init?(str: String) { - if let int = Int(str), raw = PacketType(rawValue: int) { - self = raw - } else { - return nil - } - } } - var args: [AnyObject]? { - var arr = data - - if data.count == 0 { - return nil + var args: [AnyObject] { + if type == .Event || type == .BinaryEvent && data.count != 0 { + return Array(data.dropFirst()) } else { - if type == PacketType.Event || type == PacketType.BinaryEvent { - arr.removeAtIndex(0) - return arr - } else { - return arr - } + return data } } @@ -69,7 +54,7 @@ struct SocketPacket { } var event: String { - return data[0] as? String ?? String(data[0]) + return String(data[0]) } var packetString: String { @@ -87,117 +72,106 @@ struct SocketPacket { } mutating func addData(data: NSData) -> Bool { - if placeholders == currentPlace { + if placeholders == binary.count { return true } binary.append(data) - currentPlace++ - if placeholders == currentPlace { - currentPlace = 0 + if placeholders == binary.count { + fillInPlaceholders() return true } else { return false } } - private func completeMessage(var message: String, ack: Bool) -> String { + private func completeMessage(message: String) -> String { + let restOfMessage: String + if data.count == 0 { - return message + "]" + return message + "[]" } - for arg in data { - if arg is NSDictionary || arg is [AnyObject] { - do { - let jsonSend = try NSJSONSerialization.dataWithJSONObject(arg, - options: NSJSONWritingOptions(rawValue: 0)) - let jsonString = NSString(data: jsonSend, encoding: NSUTF8StringEncoding) - - message += jsonString! as String + "," - } catch { - Logger.error("Error creating JSON object in SocketPacket.completeMessage", type: SocketPacket.logType) - } - } else if var str = arg as? String { - str = str["\n"] ~= "\\\\n" - str = str["\r"] ~= "\\\\r" - - message += "\"\(str)\"," - } else if arg is NSNull { - message += "null," - } else { - message += "\(arg)," + do { + let jsonSend = try NSJSONSerialization.dataWithJSONObject(data, + options: NSJSONWritingOptions(rawValue: 0)) + guard let jsonString = String(data: jsonSend, encoding: NSUTF8StringEncoding) else { + return "[]" } + + restOfMessage = jsonString + } catch { + DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", + type: SocketPacket.logType) + + restOfMessage = "[]" } - if message != "" { - message.removeAtIndex(message.endIndex.predecessor()) - } - - return message + "]" + return message + restOfMessage } private func createAck() -> String { - let msg: String + let message: String - if type == PacketType.Ack { + if type == .Ack { if nsp == "/" { - msg = "3\(id)[" + message = "3\(id)" } else { - msg = "3\(nsp),\(id)[" + message = "3\(nsp),\(id)" } } else { if nsp == "/" { - msg = "6\(binary.count)-\(id)[" + message = "6\(binary.count)-\(id)" } else { - msg = "6\(binary.count)-/\(nsp),\(id)[" + message = "6\(binary.count)-\(nsp),\(id)" } } - return completeMessage(msg, ack: true) + return completeMessage(message) } private func createMessageForEvent() -> String { let message: String - if type == PacketType.Event { + if type == .Event { if nsp == "/" { if id == -1 { - message = "2[" + message = "2" } else { - message = "2\(id)[" + message = "2\(id)" } } else { if id == -1 { - message = "2\(nsp),[" + message = "2\(nsp)," } else { - message = "2\(nsp),\(id)[" + message = "2\(nsp),\(id)" } } } else { if nsp == "/" { if id == -1 { - message = "5\(binary.count)-[" + message = "5\(binary.count)-" } else { - message = "5\(binary.count)-\(id)[" + message = "5\(binary.count)-\(id)" } } else { if id == -1 { - message = "5\(binary.count)-\(nsp),[" + message = "5\(binary.count)-\(nsp)," } else { - message = "5\(binary.count)-\(nsp),\(id)[" + message = "5\(binary.count)-\(nsp),\(id)" } } } - return completeMessage(message, ack: false) + return completeMessage(message) } private func createPacketString() -> String { let str: String - if type == PacketType.Event || type == PacketType.BinaryEvent { + if type == .Event || type == .BinaryEvent { str = createMessageForEvent() } else { str = createAck() @@ -206,41 +180,30 @@ struct SocketPacket { return str } - mutating func fillInPlaceholders() { - for i in 0.. AnyObject { - if let str = data as? String { - if let num = str["~~(\\d)"].groups() { - return binary[Int(num[1])!] - } else { - return str - } - } else if let dict = data as? NSDictionary { - let newDict = NSMutableDictionary(dictionary: dict) - - for (key, value) in dict { - newDict[key as! NSCopying] = _fillInPlaceholders(value) - } - - return newDict - } else if let arr = data as? NSArray { - let newArr = NSMutableArray(array: arr) - - for i in 0.. AnyObject { + switch object { + case let string as String where string["~~(\\d)"].groups() != nil: + return binary[Int(string["~~(\\d)"].groups()![1])!] + case let dict as NSDictionary: + return dict.reduce(NSMutableDictionary(), combine: {cur, keyValue in + cur[keyValue.0 as! NSCopying] = _fillInPlaceholders(keyValue.1) + return cur + }) + case let arr as [AnyObject]: + return arr.map(_fillInPlaceholders) + default: + return object } } } @@ -249,15 +212,15 @@ extension SocketPacket { private static func findType(binCount: Int, ack: Bool) -> PacketType { switch binCount { case 0 where !ack: - return PacketType.Event + return .Event case 0 where ack: - return PacketType.Ack + return .Ack case _ where !ack: - return PacketType.BinaryEvent + return .BinaryEvent case _ where ack: - return PacketType.BinaryAck + return .BinaryAck default: - return PacketType.Error + return .Error } } @@ -271,44 +234,31 @@ extension SocketPacket { } private extension SocketPacket { + // Recursive function that looks for NSData in collections static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { - if let bin = data as? NSData { - let placeholder = ["_placeholder" :true, "num": binary.count] - + let placeholder = ["_placeholder": true, "num": binary.count] + + switch data { + case let bin as NSData: binary.append(bin) - return placeholder - } else if var arr = data as? [AnyObject] { - for i in 0.. ([AnyObject], [NSData]) { + // Removes binary data from emit data + // Returns a type containing the de-binaryed data and the binary + static func deconstructData(data: [AnyObject]) -> ([AnyObject], [NSData]) { var binary = [NSData]() - for i in 0.. Bool { + return nsp == self.nsp + } + + private func handleConnect(p: SocketPacket) { + if p.nsp == "/" && nsp != "/" { + joinNamespace(nsp) + } else if p.nsp != "/" && nsp == "/" { + didConnect() + } else { + didConnect() + } + } + + private func handlePacket(pack: SocketPacket) { + switch pack.type { + case .Event where isCorrectNamespace(pack.nsp): + handleEvent(pack.event, data: pack.args, isInternalMessage: false, withAck: pack.id) + case .Ack where isCorrectNamespace(pack.nsp): + handleAck(pack.id, data: pack.data) + case .BinaryEvent where isCorrectNamespace(pack.nsp): + waitingPackets.append(pack) + case .BinaryAck where isCorrectNamespace(pack.nsp): + waitingPackets.append(pack) + case .Connect: + handleConnect(pack) + case .Disconnect: + didDisconnect("Got Disconnect") + case .Error: + handleEvent("error", data: pack.data, isInternalMessage: true, withAck: pack.id) + default: + DefaultSocketLogger.Logger.log("Got invalid packet: %@", type: "SocketParser", args: pack.description) + } + } + + /// Parses a messsage from the engine. Returning either a string error or a complete SocketPacket + func parseString(message: String) -> Either { + var parser = SocketStringReader(message: message) + + guard let type = SocketPacket.PacketType(rawValue: Int(parser.read(1)) ?? -1) else { + return .Left("Invalid packet type") + } + + if !parser.hasNext { + return .Right(SocketPacket(type: type, nsp: "/")) + } + + var namespace = "/" + var placeholders = -1 + + if type == .BinaryEvent || type == .BinaryAck { + if let holders = Int(parser.readUntilStringOccurence("-")) { + placeholders = holders + } else { + return .Left("Invalid packet") + } + } + + if parser.currentCharacter == "/" { + namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() + } + + if !parser.hasNext { + return .Right(SocketPacket(type: type, id: -1, + nsp: namespace ?? "/", placeholders: placeholders)) + } + + var idString = "" + + if type == .Error { + parser.advanceIndexBy(-1) + } + + while parser.hasNext && type != .Error { + if let int = Int(parser.read(1)) { + idString += String(int) + } else { + parser.advanceIndexBy(-2) + break + } + } + + let d = message[parser.currentIndex.advancedBy(1).. Either { + let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + do { + if let arr = try NSJSONSerialization.JSONObjectWithData(stringData!, + options: NSJSONReadingOptions.MutableContainers) as? [AnyObject] { + return .Right(arr) + } else { + return .Left("Expected data array") + } + } catch { + return .Left("Error parsing data for packet") + } + } + + // Parses messages recieved + func parseSocketMessage(message: String) { + guard !message.isEmpty else { return } + + DefaultSocketLogger.Logger.log("Parsing %@", type: "SocketParser", args: message) + + switch parseString(message) { + case let .Left(err): + DefaultSocketLogger.Logger.error("\(err): %@", type: "SocketParser", args: message) + case let .Right(pack): + DefaultSocketLogger.Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) + handlePacket(pack) + } + } + + func parseBinaryData(data: NSData) { + guard !waitingPackets.isEmpty else { + DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") + return + } + + // Should execute event? + guard waitingPackets[waitingPackets.count - 1].addData(data) else { + return + } + + let packet = waitingPackets.removeLast() + + if packet.type != .BinaryAck { + handleEvent(packet.event, data: packet.args ?? [], + isInternalMessage: false, withAck: packet.id) + } else { + handleAck(packet.id, data: packet.args) + } + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketParser.swift b/RNSwiftSocketIO/SocketIOClient/SocketParser.swift deleted file mode 100755 index cd79bde..0000000 --- a/RNSwiftSocketIO/SocketIOClient/SocketParser.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// SocketParser.swift -// Socket.IO-Client-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 { - - private static func isCorrectNamespace(nsp: String, _ socket: SocketIOClient) -> Bool { - return nsp == socket.nsp - } - - private static func handleEvent(p: SocketPacket, socket: SocketIOClient) { - guard isCorrectNamespace(p.nsp, socket) else { return } - - socket.handleEvent(p.event, data: p.args ?? [], - isInternalMessage: false, wantsAck: p.id) - } - - private static func handleAck(p: SocketPacket, socket: SocketIOClient) { - guard isCorrectNamespace(p.nsp, socket) else { return } - - socket.handleAck(p.id, data: p.data) - } - - private static func handleBinary(p: SocketPacket, socket: SocketIOClient) { - guard isCorrectNamespace(p.nsp, socket) else { return } - - socket.waitingData.append(p) - } - - private static func handleConnect(p: SocketPacket, socket: SocketIOClient) { - if p.nsp == "/" && socket.nsp != "/" { - socket.joinNamespace() - } else if p.nsp != "/" && socket.nsp == "/" { - socket.didConnect() - } else { - socket.didConnect() - } - } - - static func parseString(message: String) -> SocketPacket? { - var parser = SocketStringReader(message: message) - - guard let type = SocketPacket.PacketType(str: parser.read(1)) - else {return nil} - - if !parser.hasNext { - return SocketPacket(type: type, nsp: "/") - } - - var namespace: String? - var placeholders = -1 - - if type == .BinaryEvent || type == .BinaryAck { - if let holders = Int(parser.readUntilStringOccurence("-")) { - placeholders = holders - } else { - return nil - } - } - - if parser.currentCharacter == "/" { - namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() - } - - if !parser.hasNext { - return SocketPacket(type: type, id: -1, - nsp: namespace ?? "/", placeholders: placeholders) - } - - var idString = "" - - while parser.hasNext { - if let int = Int(parser.read(1)) { - idString += String(int) - } else { - parser.advanceIndexBy(-2) - break - } - } - - let d = message[parser.currentIndex.advancedBy(1).. AnyObject? { - let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - do { - return try NSJSONSerialization.JSONObjectWithData(stringData!, - options: NSJSONReadingOptions.MutableContainers) - } catch { - Logger.error("Parsing JSON: %@", type: "SocketParser", args: data) - return nil - } - } - - // Parses messages recieved - static func parseSocketMessage(message: String, socket: SocketIOClient) { - guard !message.isEmpty else { return } - - Logger.log("Parsing %@", type: "SocketParser", args: message) - - guard let pack = parseString(message) else { - Logger.error("Parsing message: %@", type: "SocketParser", args: message) - return - } - - Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) - - switch pack.type { - case .Event: - handleEvent(pack, socket: socket) - case .Ack: - handleAck(pack, socket: socket) - case .BinaryEvent: - handleBinary(pack, socket: socket) - case .BinaryAck: - handleBinary(pack, socket: socket) - case .Connect: - handleConnect(pack, socket: socket) - case .Disconnect: - socket.didDisconnect("Got Disconnect") - case .Error: - socket.didError("Error: \(pack.data)") - } - - } - - static func parseBinaryData(data: NSData, socket: SocketIOClient) { - guard !socket.waitingData.isEmpty else { - Logger.error("Got data when not remaking packet", type: "SocketParser") - return - } - - let shouldExecute = socket.waitingData[socket.waitingData.count - 1].addData(data) - - guard shouldExecute else { - return - } - - var packet = socket.waitingData.removeLast() - packet.fillInPlaceholders() - - if packet.type != .BinaryAck { - socket.handleEvent(packet.event, data: packet.args ?? [], - isInternalMessage: false, wantsAck: packet.id) - } else { - socket.handleAck(packet.id, data: packet.args) - } - } -} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift b/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift old mode 100755 new mode 100644 index e3b2d69..d1e2b59 --- a/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift @@ -65,4 +65,4 @@ struct SocketStringReader { mutating func readUntilEnd() -> String { return read(currentIndex.distanceTo(message.endIndex)) } -} \ No newline at end of file +} diff --git a/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift b/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift old mode 100755 new mode 100644 index 09fb67a..b8840be --- a/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift +++ b/RNSwiftSocketIO/SocketIOClient/SocketTypes.swift @@ -25,6 +25,10 @@ import Foundation public typealias AckCallback = ([AnyObject]) -> Void -public typealias NormalCallback = ([AnyObject], SocketAckEmitter?) -> Void +public typealias NormalCallback = ([AnyObject], SocketAckEmitter) -> Void public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void +enum Either { + case Left(E) + case Right(V) +} diff --git a/RNSwiftSocketIO/SocketIOClient/String.swift b/RNSwiftSocketIO/SocketIOClient/String.swift new file mode 100644 index 0000000..0e30e8c --- /dev/null +++ b/RNSwiftSocketIO/SocketIOClient/String.swift @@ -0,0 +1,31 @@ +// +// String.swift +// Socket.IO-Client-Swift +// +// Created by Yannick Loriot on 5/4/16. +// +// 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 + +extension String { + func urlEncode() -> String? { + return stringByAddingPercentEncodingWithAllowedCharacters(.allowedURLCharacterSet) + } +} diff --git a/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift b/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift old mode 100755 new mode 100644 index b088918..b704afd --- a/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift +++ b/RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift @@ -13,14 +13,32 @@ import Foundation +infix operator <~ { associativity none precedence 130 } + +private let lock = dispatch_semaphore_create(1) private var swiftRegexCache = [String: NSRegularExpression]() -internal class SwiftRegex: NSObject, BooleanType { - var target:String +internal final class SwiftRegex : NSObject, BooleanType { + var target: String var regex: NSRegularExpression init(target:String, pattern:String, options:NSRegularExpressionOptions?) { self.target = target + + if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { + do { + let regex = try NSRegularExpression(pattern: pattern, options: + NSRegularExpressionOptions.DotMatchesLineSeparators) + self.regex = regex + } catch let error as NSError { + SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") + self.regex = NSRegularExpression() + } + + super.init() + return + } + if let regex = swiftRegexCache[pattern] { self.regex = regex } else { @@ -34,6 +52,7 @@ internal class SwiftRegex: NSObject, BooleanType { self.regex = NSRegularExpression() } } + dispatch_semaphore_signal(lock) super.init() } @@ -41,11 +60,11 @@ internal class SwiftRegex: NSObject, BooleanType { fatalError("SwiftRegex: \(message)") } - private final var targetRange: NSRange { + private var targetRange: NSRange { return NSRange(location: 0,length: target.utf16.count) } - private final func substring(range: NSRange) -> String? { + private func substring(range: NSRange) -> String? { if range.location != NSNotFound { return (target as NSString).substringWithRange(range) } else { @@ -168,36 +187,9 @@ extension String { } } -func ~= (left: SwiftRegex, right: String) -> String { +func <~ (left: SwiftRegex, right: String) -> String { return left.substituteMatches({match, stop in return left.regex.replacementStringForResult( match, inString: left.target as String, offset: 0, template: right ) }, options: []) } - -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: []) -} - -func ~= (left: SwiftRegex, right: (String) -> String) -> String { - // return right(left.substring(match.range)) - return left.substituteMatches( - {match, stop -> String in - right(left.substring(match.range)!) - }, options: []) -} - -func ~= (left: SwiftRegex, right: ([String]?) -> String) -> String { - return left.substituteMatches({match, stop -> String in - return right(left.groupsForMatch(match)) - }, options: []) -} diff --git a/RNSwiftSocketIO/SocketIOClient/WebSocket.swift b/RNSwiftSocketIO/SocketIOClient/WebSocket.swift old mode 100755 new mode 100644 index 016be75..833eece --- a/RNSwiftSocketIO/SocketIOClient/WebSocket.swift +++ b/RNSwiftSocketIO/SocketIOClient/WebSocket.swift @@ -3,11 +3,25 @@ // Websocket.swift // // Created by Dalton Cherry on 7/16/14. +// Copyright (c) 2014-2015 Dalton Cherry. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // ////////////////////////////////////////////////////////////////////////////////////////////////// import Foundation import CoreFoundation +import Security public protocol WebSocketDelegate: class { func websocketDidConnect(socket: WebSocket) @@ -33,7 +47,7 @@ public class WebSocket : NSObject, NSStreamDelegate { //B-F reserved. } - enum CloseCode : UInt16 { + public enum CloseCode : UInt16 { case Normal = 1000 case GoingAway = 1001 case ProtocolError = 1002 @@ -46,6 +60,8 @@ public class WebSocket : NSObject, NSStreamDelegate { case MessageTooBig = 1009 } + public static let ErrorDomain = "WebSocket" + enum InternalErrorCode : UInt16 { // 0-999 WebSocket status codes not used case OutputStreamWriteError = 1 @@ -54,7 +70,7 @@ public class WebSocket : NSObject, NSStreamDelegate { //Where the callback is executed. It defaults to the main UI thread queue. public var queue = dispatch_get_main_queue() - var optionalProtocols : Array? + var optionalProtocols : [String]? //Constant Values. let headerWSUpgradeName = "Upgrade" let headerWSUpgradeValue = "websocket" @@ -90,142 +106,155 @@ public class WebSocket : NSObject, NSStreamDelegate { public var onText: ((String) -> Void)? public var onData: ((NSData) -> Void)? public var onPong: ((Void) -> Void)? - public var headers = Dictionary() + public var headers = [String: String]() public var voipEnabled = false public var selfSignedSSL = false - private var security: Security? + public var security: SSLSecurity? + public var enabledSSLCipherSuites: [SSLCipherSuite]? + public var origin: String? public var isConnected :Bool { return connected } - - private var cookies:[NSHTTPCookie]? + public var currentURL: NSURL {return url} private var url: NSURL private var inputStream: NSInputStream? private var outputStream: NSOutputStream? - private var isRunLoop = false private var connected = false private var isCreated = false - private var writeQueue: NSOperationQueue? - private var readStack = Array() - private var inputQueue = Array() + private var writeQueue = NSOperationQueue() + private var readStack = [WSResponse]() + private var inputQueue = [NSData]() private var fragBuffer: NSData? private var certValidated = false private var didDisconnect = false - - //init the websocket with a url - public init(url: NSURL) { - self.url = url - } - - public convenience init(url: NSURL, cookies:[NSHTTPCookie]?) { - self.init(url: url) - self.cookies = cookies + private var readyToWrite = false + private let mutex = NSLock() + private var canDispatch: Bool { + mutex.lock() + let canWork = readyToWrite + mutex.unlock() + return canWork } + //the shared processing queue used for all websocket + private static let sharedWorkQueue = dispatch_queue_create("com.vluxe.starscream.websocket", DISPATCH_QUEUE_SERIAL) //used for setting protocols. - public convenience init(url: NSURL, protocols: Array) { - self.init(url: url) + public init(url: NSURL, protocols: [String]? = nil) { + self.url = url + self.origin = url.absoluteString + writeQueue.maxConcurrentOperationCount = 1 optionalProtocols = protocols } ///Connect to the websocket server on a background thread public func connect() { - if isCreated { - return - } - - dispatch_async(queue, { [weak self] in - guard let weakSelf = self else { - return - } - - weakSelf.didDisconnect = false - }) - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { [weak self] in - guard let weakSelf = self else { - return - } - - weakSelf.isCreated = true - weakSelf.createHTTPRequest() - weakSelf.isCreated = false - }) + guard !isCreated else { return } + didDisconnect = false + isCreated = true + createHTTPRequest() + isCreated = false } - ///disconnect from the websocket server - public func disconnect() { - writeError(CloseCode.Normal.rawValue) + /** + Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed. + + If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate. + + If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate. + + - Parameter forceTimeout: Maximum time to wait for the server to close the socket. + */ + public func disconnect(forceTimeout forceTimeout: NSTimeInterval? = nil) { + switch forceTimeout { + case .Some(let seconds) where seconds > 0: + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(seconds * Double(NSEC_PER_SEC))), queue) { [weak self] in + self?.disconnectStream(nil) + } + fallthrough + case .None: + writeError(CloseCode.Normal.rawValue) + + default: + self.disconnectStream(nil) + break + } } - ///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 a string to the websocket. This sends it as a text frame. + + If you supply a non-nil completion block, I will perform it when the write completes. + - parameter str: The string to write. + - parameter completion: The (optional) completion handler. + */ + public func writeString(str: String, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame, writeCompletion: completion) } - ///write binary data to the websocket. This sends it as a binary frame. - public func writeData(data: NSData) { - dequeueWrite(data, code: .BinaryFrame) + /** + Write binary data to the websocket. This sends it as a binary frame. + + If you supply a non-nil completion block, I will perform it when the write completes. + - parameter data: The data to write. + - parameter completion: The (optional) completion handler. + */ + public func writeData(data: NSData, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(data, code: .BinaryFrame, writeCompletion: completion) } //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) + public func writePing(data: NSData, completion: (() -> ())? = nil) { + guard isConnected else { return } + dequeueWrite(data, code: .Ping, writeCompletion: completion) } - //private methods below! //private method that starts the connection private func createHTTPRequest() { let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", - url, kCFHTTPVersion1_1).takeRetainedValue() + url, kCFHTTPVersion1_1).takeRetainedValue() var port = url.port if port == nil { - if url.scheme == "wss" || url.scheme == "https" { + if ["wss", "https"].contains(url.scheme) { port = 443 } else { port = 80 } } - - if self.cookies != nil { - let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(self.cookies!) - for (key, value) in headers { - self.addHeader(urlRequest, key: key as String, val: value as String) - } - } - - self.addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) - self.addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) + addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) + addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) if let protocols = optionalProtocols { - self.addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) + addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) + } + addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) + addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) + if let origin = origin { + addHeader(urlRequest, key: headerOriginName, val: origin) } - 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!)") + addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") for (key,value) in headers { - self.addHeader(urlRequest, key: key, val: value) + addHeader(urlRequest, key: key, val: value) + } + if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) { + let serializedRequest = cfHTTPMessage.takeRetainedValue() + initStreamsWithData(serializedRequest, Int(port!)) } - - - let serializedRequest: NSData = CFHTTPMessageCopySerializedMessage(urlRequest)!.takeRetainedValue() - self.initStreamsWithData(serializedRequest, Int(port!)) } + //Add a header to the CFHTTPMessage by using the NSString bridges to CFString - private func addHeader(urlRequest: CFHTTPMessage,key: String, val: String) { - let nsKey: NSString = key - let nsVal: NSString = val - CFHTTPMessageSetHeaderFieldValue(urlRequest, - nsKey, - nsVal) + private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) { + CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val) } + //generate a websocket key as needed in rfc private func generateWebSocketKey() -> String { var key = "" let seed = 16 - for (var i = 0; i < seed; i++) { + for _ in 0.. = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] - inputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) - outputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) + if let cipherSuites = self.enabledSSLCipherSuites { + if let sslContextIn = CFReadStreamCopyProperty(inputStream, kCFStreamPropertySSLContext) as! SSLContextRef?, + sslContextOut = CFWriteStreamCopyProperty(outputStream, kCFStreamPropertySSLContext) as! SSLContextRef? { + let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count) + let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count) + if resIn != errSecSuccess { + let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)) + disconnectStream(error) + return + } + if resOut != errSecSuccess { + let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)) + disconnectStream(error) + return + } + } } - isRunLoop = true - inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) - outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) - inputStream!.open() - outputStream!.open() + CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) + CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) + inStream.open() + outStream.open() + + self.mutex.lock() + self.readyToWrite = true + self.mutex.unlock() + let bytes = UnsafePointer(data.bytes) - outputStream!.write(bytes, maxLength: data.length) - while(isRunLoop) { - NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as NSDate) + var timeout = 5000000 //wait 5 seconds before giving up + writeQueue.addOperationWithBlock { [weak self] in + while !outStream.hasSpaceAvailable { + usleep(100) //wait until the socket is ready + timeout -= 100 + if timeout < 0 { + self?.cleanupStream() + self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) + return + } else if outStream.streamError != nil { + return //disconnectStream will be called. + } + } + outStream.write(bytes, maxLength: data.length) } } //delegate for the stream methods. Processes incoming bytes public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { - if let sec = security where !certValidated && (eventCode == .HasBytesAvailable || eventCode == .HasSpaceAvailable) { + if let sec = security where !certValidated && [.HasBytesAvailable, .HasSpaceAvailable].contains(eventCode) { let possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as String) if let trust: AnyObject = possibleTrust { let domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as String) if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) { certValidated = true } else { - let error = self.errorWithDetail("Invalid SSL certificate", code: 1) - doDisconnect(error) + let error = errorWithDetail("Invalid SSL certificate", code: 1) disconnectStream(error) return } } } if eventCode == .HasBytesAvailable { - if(aStream == inputStream) { + if aStream == inputStream { processInputStream() } } else if eventCode == .ErrorOccurred { @@ -302,22 +363,28 @@ public class WebSocket : NSObject, NSStreamDelegate { } //disconnect the stream object private func disconnectStream(error: NSError?) { - if writeQueue != nil { - writeQueue!.waitUntilAllOperationsAreFinished() + if error == nil { + writeQueue.waitUntilAllOperationsAreFinished() + } else { + writeQueue.cancelAllOperations() } + cleanupStream() + doDisconnect(error) + } + + private func cleanupStream() { + outputStream?.delegate = nil + inputStream?.delegate = nil if let stream = inputStream { - stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + CFReadStreamSetDispatchQueue(stream, nil) stream.close() } if let stream = outputStream { - stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) + CFWriteStreamSetDispatchQueue(stream, nil) stream.close() } outputStream = nil - isRunLoop = false - certValidated = false - self.doDisconnect(error) - connected = false + inputStream = nil } ///handles the incoming bytes and sending them to the proper processing method @@ -325,49 +392,67 @@ public class WebSocket : NSObject, NSStreamDelegate { let buf = NSMutableData(capacity: BUFFER_MAX) let buffer = UnsafeMutablePointer(buf!.bytes) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) - if length > 0 { - if !connected { - let status = processHTTP(buffer, bufferLen: length) - if !status { - self.doDisconnect(self.errorWithDetail("Invalid HTTP upgrade", code: 1)) - } - } else { - var process = false - if inputQueue.count == 0 { - process = true - } - inputQueue.append(NSData(bytes: buffer, length: length)) - if process { - dequeueInput() - } - } + + guard length > 0 else { return } + 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) { - let 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() + guard !inputQueue.isEmpty else { return } + + let data = inputQueue[0] + var work = data + if let fragBuffer = fragBuffer { + let combine = NSMutableData(data: fragBuffer) + combine.appendData(data) + work = combine + self.fragBuffer = nil + } + let buffer = UnsafePointer(work.bytes) + let length = work.length + if !connected { + processTCPHandshake(buffer, bufferLen: length) + } else { + processRawMessage(buffer, bufferLen: length) + } + inputQueue = inputQueue.filter{$0 != data} + dequeueInput() + } + + //handle checking the inital connection status + private func processTCPHandshake(buffer: UnsafePointer, bufferLen: Int) { + let code = processHTTP(buffer, bufferLen: bufferLen) + switch code { + case 0: + connected = true + guard canDispatch else {return} + dispatch_async(queue) { [weak self] in + guard let s = self else { return } + s.onConnect?() + s.delegate?.websocketDidConnect(s) + } + case -1: + fragBuffer = NSData(bytes: buffer, length: bufferLen) + break //do nothing, we are going to collect more data + default: + doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code))) } } ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. - private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Bool { + private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Int { 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++ { + for i in 0.. 0 { - if validateResponse(buffer, bufferLen: totalSize) { - dispatch_async(queue, { - self.connected = true - if let connectBlock = self.onConnect { - 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 + let code = validateResponse(buffer, bufferLen: totalSize) + if code != 0 { + return code + } + totalSize += 1 //skip the last \n + let restSize = bufferLen - totalSize + if restSize > 0 { + processRawMessage((buffer+totalSize),bufferLen: restSize) } + return 0 //success } - return false + return -1 //was unable to find the full TCP header } ///validates the HTTP is a 101 as per the RFC spec - private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Bool { + private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Int { let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() CFHTTPMessageAppendBytes(response, buffer, bufferLen) - if CFHTTPMessageGetResponseStatusCode(response) != 101 { - return false + let code = CFHTTPMessageGetResponseStatusCode(response) + if code != 101 { + return code } - let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) - let headers:NSDictionary? = cfHeaders?.takeRetainedValue() - let acceptKey = headers?[headerWSAcceptName] as! NSString - if acceptKey.length > 0 { - return true + if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { + let headers = cfHeaders.takeRetainedValue() as NSDictionary + if let acceptKey = headers[headerWSAcceptName] as? NSString { + if acceptKey.length > 0 { + return 0 + } + } + } + return -1 + } + + ///read a 16 bit big endian value from a buffer + private static func readUint16(buffer: UnsafePointer, offset: Int) -> UInt16 { + return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1]) + } + + ///read a 64 bit big endian value from a buffer + private static func readUint64(buffer: UnsafePointer, offset: Int) -> UInt64 { + var value = UInt64(0) + for i in 0...7 { + value = (value << 8) | UInt64(buffer[offset + i]) + } + return value + } + + ///write a 16 bit big endian value to a buffer + private static func writeUint16(buffer: UnsafeMutablePointer, offset: Int, value: UInt16) { + buffer[offset + 0] = UInt8(value >> 8) + buffer[offset + 1] = UInt8(value & 0xff) + } + + ///write a 64 bit big endian value to a buffer + private static func writeUint64(buffer: UnsafeMutablePointer, offset: Int, value: UInt64) { + for i in 0...7 { + buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff) } - return false } ///process the websocket data @@ -419,17 +529,16 @@ public class WebSocket : NSObject, NSStreamDelegate { 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 { + if let response = response where response.bytesLeft > 0 { + var len = response.bytesLeft + var extra = bufferLen - response.bytesLeft + if response.bytesLeft > bufferLen { len = bufferLen extra = 0 } - resp.bytesLeft -= len - resp.buffer?.appendData(NSData(bytes: buffer, length: len)) - processResponse(resp) + response.bytesLeft -= len + response.buffer?.appendData(NSData(bytes: buffer, length: len)) + processResponse(response) let offset = bufferLen - extra if extra > 0 { processExtra((buffer+offset), bufferLen: extra) @@ -437,40 +546,36 @@ public class WebSocket : NSObject, NSStreamDelegate { return } else { let isFin = (FinMask & buffer[0]) - let receivedOpcode = (OpCodeMask & buffer[0]) + let receivedOpcode = OpCode(rawValue: (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) { + if (isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != .Pong { let errCode = CloseCode.ProtocolError.rawValue - let error = self.errorWithDetail("masked and rsv data is not currently supported", code: errCode) - self.doDisconnect(error) + doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode)) 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) - self.doDisconnect(error) - writeError(errCode) - return + let isControlFrame = (receivedOpcode == .ConnectionClose || receivedOpcode == .Ping) + if !isControlFrame && (receivedOpcode != .BinaryFrame && receivedOpcode != .ContinueFrame && + receivedOpcode != .TextFrame && receivedOpcode != .Pong) { + let errCode = CloseCode.ProtocolError.rawValue + doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode)) + writeError(errCode) + return } if isControlFrame && isFin == 0 { let errCode = CloseCode.ProtocolError.rawValue - let error = self.errorWithDetail("control frames can't be fragmented", code: errCode) - self.doDisconnect(error) + doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode)) writeError(errCode) return } - if receivedOpcode == OpCode.ConnectionClose.rawValue { + if receivedOpcode == .ConnectionClose { var code = CloseCode.Normal.rawValue if payloadLen == 1 { code = CloseCode.ProtocolError.rawValue } else if payloadLen > 1 { - let codeBuffer = UnsafePointer((buffer+offset)) - code = codeBuffer[0].bigEndian + code = WebSocket.readUint16(buffer, offset: offset) if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { code = CloseCode.ProtocolError.rawValue } @@ -486,8 +591,7 @@ public class WebSocket : NSObject, NSStreamDelegate { } } } - let error = self.errorWithDetail("connection closed by server", code: code) - self.doDisconnect(error) + doDisconnect(errorWithDetail("connection closed by server", code: code)) writeError(code) return } @@ -497,12 +601,10 @@ public class WebSocket : NSObject, NSStreamDelegate { } var dataLength = UInt64(payloadLen) if dataLength == 127 { - let bytes = UnsafePointer((buffer+offset)) - dataLength = bytes[0].bigEndian + dataLength = WebSocket.readUint64(buffer, offset: offset) offset += sizeof(UInt64) } else if dataLength == 126 { - let bytes = UnsafePointer((buffer+offset)) - dataLength = UInt64(bytes[0].bigEndian) + dataLength = UInt64(WebSocket.readUint16(buffer, offset: offset)) offset += sizeof(UInt16) } if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { @@ -513,19 +615,21 @@ public class WebSocket : NSObject, NSStreamDelegate { if dataLength > UInt64(bufferLen) { len = UInt64(bufferLen-offset) } - var data: NSData! + let data: NSData if len < 0 { len = 0 data = NSData() } else { data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) } - if receivedOpcode == OpCode.Pong.rawValue { - dispatch_async(queue, { - self.onPong?() - self.pongDelegate?.websocketDidReceivePong(self) - }) - + if receivedOpcode == .Pong { + if canDispatch { + dispatch_async(queue) { [weak self] in + guard let s = self else { return } + s.onPong?() + s.pongDelegate?.websocketDidReceivePong(s) + } + } let step = Int(offset+numericCast(len)) let extra = bufferLen-step if extra > 0 { @@ -537,54 +641,51 @@ public class WebSocket : NSObject, NSStreamDelegate { if isControlFrame { response = nil //don't append pings } - if isFin == 0 && receivedOpcode == OpCode.ContinueFrame.rawValue && response == nil { + if isFin == 0 && receivedOpcode == .ContinueFrame && response == nil { let errCode = CloseCode.ProtocolError.rawValue - let error = self.errorWithDetail("continue frame before a binary or text frame", code: errCode) - self.doDisconnect(error) + doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode)) writeError(errCode) return } var isNew = false - if(response == nil) { - if receivedOpcode == OpCode.ContinueFrame.rawValue { + if response == nil { + if receivedOpcode == .ContinueFrame { let errCode = CloseCode.ProtocolError.rawValue - let error = self.errorWithDetail("first frame can't be a continue frame", - code: errCode) - self.doDisconnect(error) + doDisconnect(errorWithDetail("first frame can't be a continue frame", + code: errCode)) writeError(errCode) return } isNew = true response = WSResponse() - response!.code = OpCode(rawValue: receivedOpcode)! + response!.code = receivedOpcode! response!.bytesLeft = Int(dataLength) response!.buffer = NSMutableData(data: data) } else { - if receivedOpcode == OpCode.ContinueFrame.rawValue { + if receivedOpcode == .ContinueFrame { 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) - self.doDisconnect(error) + doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame", + code: errCode)) 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!) + if let response = response { + response.bytesLeft -= Int(len) + response.frameCount += 1 + response.isFin = isFin > 0 ? true : false + if isNew { + readStack.append(response) } - processResponse(response!) + processResponse(response) } let step = Int(offset+numericCast(len)) let extra = bufferLen-step - if(extra > 0) { + if extra > 0 { processExtra((buffer+step), bufferLen: extra) } } @@ -608,24 +709,27 @@ public class WebSocket : NSObject, NSStreamDelegate { dequeueWrite(data, code: OpCode.Pong) } else if response.code == .TextFrame { let str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) - - if let str = str as String? { - dispatch_async(queue, { - self.onText?(str) - self.delegate?.websocketDidReceiveMessage(self, text: str) - }) - } else { + if str == nil { writeError(CloseCode.Encoding.rawValue) return false } + if canDispatch { + dispatch_async(queue) { [weak self] in + guard let s = self else { return } + s.onText?(str! as String) + s.delegate?.websocketDidReceiveMessage(s, 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.onData?(data) - self.delegate?.websocketDidReceiveData(self, data: data) + if canDispatch { + let data = response.buffer! //local copy so it is perverse for writing + dispatch_async(queue) { [weak self] in + guard let s = self else { return } + s.onData?(data) + s.delegate?.websocketDidReceiveData(s, data: data) + } } } - readStack.removeLast() return true } @@ -634,88 +738,74 @@ public class WebSocket : NSObject, NSStreamDelegate { ///Create an error private func errorWithDetail(detail: String, code: UInt16) -> NSError { - var details = Dictionary() + var details = [String: String]() details[NSLocalizedDescriptionKey] = detail - return NSError(domain: "Websocket", code: Int(code), userInfo: details) + return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) } ///write a an error to the socket private func writeError(code: UInt16) { let buf = NSMutableData(capacity: sizeof(UInt16)) - let buffer = UnsafeMutablePointer(buf!.bytes) - buffer[0] = code.bigEndian + let buffer = UnsafeMutablePointer(buf!.bytes) + WebSocket.writeUint16(buffer, offset: 0, value: code) dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) } ///used to write things to the stream - private func dequeueWrite(data: NSData, code: OpCode) { - if writeQueue == nil { - writeQueue = NSOperationQueue() - writeQueue!.maxConcurrentOperationCount = 1 - } - writeQueue!.addOperationWithBlock { + private func dequeueWrite(data: NSData, code: OpCode, writeCompletion: (() -> ())? = nil) { + writeQueue.addOperationWithBlock { [weak self] in //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 - } + guard let s = self else { return } var offset = 2 - UINT16_MAX let bytes = UnsafeMutablePointer(data.bytes) let dataLength = data.length - let frame = NSMutableData(capacity: dataLength + self.MaxFrameSize) + let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize) let buffer = UnsafeMutablePointer(frame!.mutableBytes) - buffer[0] = self.FinMask | code.rawValue + buffer[0] = s.FinMask | code.rawValue if dataLength < 126 { buffer[1] = CUnsignedChar(dataLength) } else if dataLength <= Int(UInt16.max) { buffer[1] = 126 - let sizeBuffer = UnsafeMutablePointer((buffer+offset)) - sizeBuffer[0] = UInt16(dataLength).bigEndian + WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength)) offset += sizeof(UInt16) } else { buffer[1] = 127 - let sizeBuffer = UnsafeMutablePointer((buffer+offset)) - sizeBuffer[0] = UInt64(dataLength).bigEndian + WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength)) offset += sizeof(UInt64) } - buffer[1] |= self.MaskMask + buffer[1] |= s.MaskMask let maskKey = UnsafeMutablePointer(buffer + offset) SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) offset += sizeof(UInt32) - for (var i = 0; i < dataLength; i++) { + for i in 0..(frame!.bytes+total) - let len = self.outputStream?.write(writeBuffer, maxLength: offset-total) - if len == nil || len! < 0 { + let len = outStream.write(writeBuffer, maxLength: offset-total) + if len < 0 { var error: NSError? - if let streamError = self.outputStream?.streamError { + if let streamError = outStream.streamError { error = streamError } else { let errCode = InternalErrorCode.OutputStreamWriteError.rawValue - error = self.errorWithDetail("output stream error during write", code: errCode) + error = s.errorWithDetail("output stream error during write", code: errCode) } - self.doDisconnect(error) + s.doDisconnect(error) break } else { - total += len! + total += len } if total >= offset { + if let queue = self?.queue, callback = writeCompletion { + dispatch_async(queue) { + callback() + } + } + break } } @@ -725,60 +815,55 @@ public class WebSocket : NSObject, NSStreamDelegate { ///used to preform the disconnect delegate private func doDisconnect(error: NSError?) { - if !self.didDisconnect { - dispatch_async(queue) { - self.didDisconnect = true - - self.onDisconnect?(error) - self.delegate?.websocketDidDisconnect(self, error: error) - } + guard !didDisconnect else { return } + didDisconnect = true + connected = false + guard canDispatch else {return} + dispatch_async(queue) { [weak self] in + guard let s = self else { return } + s.onDisconnect?(error) + s.delegate?.websocketDidDisconnect(s, error: error) } } + deinit { + mutex.lock() + readyToWrite = false + mutex.unlock() + cleanupStream() + } + } -////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Security.swift -// Starscream -// -// Created by Dalton Cherry on 5/16/15. -// Copyright (c) 2015 Vluxe. All rights reserved. -// -////////////////////////////////////////////////////////////////////////////////////////////////// - -import Foundation -import Security - -private class SSLCert { +public class SSLCert { var certData: NSData? var key: SecKeyRef? /** - Designated init for certificates - - :param: data is the binary data of the certificate - - :returns: a representation security object to be used with - */ - init(data: NSData) { + Designated init for certificates + + - parameter data: is the binary data of the certificate + + - returns: a representation security object to be used with + */ + public init(data: NSData) { self.certData = data } /** - Designated init for public keys - - :param: key is the public key to be used - - :returns: a representation security object to be used with - */ - init(key: SecKeyRef) { + Designated init for public keys + + - parameter key: is the public key to be used + + - returns: a representation security object to be used with + */ + public init(key: SecKeyRef) { self.key = key } } -private class Security { - private var validatedDN = true //should the domain name be validated? +public class SSLSecurity { + public var validatedDN = true //should the domain name be validated? var isReady = false //is the key processing done? var certificates: [NSData]? //the certificates @@ -788,67 +873,73 @@ private class Security { /** Use certs from main app bundle - :param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation - :returns: a representation security object to be used with + - returns: a representation security object to be used with */ - private convenience init(usePublicKeys: Bool = false) { + public convenience init(usePublicKeys: Bool = false) { let paths = NSBundle.mainBundle().pathsForResourcesOfType("cer", inDirectory: ".") - var collect = Array() - for path in paths { - if let d = NSData(contentsOfFile: path as String) { - collect.append(SSLCert(data: d)) + + let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in + var certs = certs + if let data = NSData(contentsOfFile: path) { + certs.append(SSLCert(data: data)) } + return certs } - self.init(certs:collect, usePublicKeys: usePublicKeys) + + self.init(certs: certs, usePublicKeys: usePublicKeys) } /** - Designated init - - :param: keys is the certificates or public keys to use - :param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation - - :returns: a representation security object to be used with - */ - private init(certs: [SSLCert], usePublicKeys: Bool) { + Designated init + + - parameter keys: is the certificates or public keys to use + - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation + + - returns: a representation security object to be used with + */ + public init(certs: [SSLCert], usePublicKeys: Bool) { self.usePublicKeys = usePublicKeys if self.usePublicKeys { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { - var collect = Array() - for cert in certs { - if let data = cert.certData where cert.key == nil { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { + let pubKeys = certs.reduce([SecKeyRef]()) { (pubKeys: [SecKeyRef], cert: SSLCert) -> [SecKeyRef] in + var pubKeys = pubKeys + if let data = cert.certData where cert.key == nil { cert.key = self.extractPublicKey(data) } - if let k = cert.key { - collect.append(k) + if let key = cert.key { + pubKeys.append(key) } + return pubKeys } - self.pubKeys = collect + + self.pubKeys = pubKeys self.isReady = true - }) + } } else { - var collect = Array() - for cert in certs { - if let d = cert.certData { - collect.append(d) + let certificates = certs.reduce([NSData]()) { (certificates: [NSData], cert: SSLCert) -> [NSData] in + var certificates = certificates + if let data = cert.certData { + certificates.append(data) } + return certificates } - self.certificates = collect + self.certificates = certificates self.isReady = true } } /** - Valid the trust and domain name. - - :param: trust is the serverTrust to validate - :param: domain is the CN domain to validate - - :returns: if the key was successfully validated - */ - private func isValid(trust: SecTrustRef, domain: String?) -> Bool { + Valid the trust and domain name. + + - parameter trust: is the serverTrust to validate + - parameter domain: is the CN domain to validate + + - returns: if the key was successfully validated + */ + public func isValid(trust: SecTrustRef, domain: String?) -> Bool { var tries = 0 while(!self.isReady) { @@ -867,23 +958,18 @@ private class Security { SecTrustSetPolicies(trust,policy) if self.usePublicKeys { if let keys = self.pubKeys { - var trustedCount = 0 let serverPubKeys = publicKeyChainForTrust(trust) for serverKey in serverPubKeys as [AnyObject] { for key in keys as [AnyObject] { if serverKey.isEqual(key) { - trustedCount++ - break + return true } } } - if trustedCount == serverPubKeys.count { - return true - } } } else if let certs = self.certificates { let serverCerts = certificateChainForTrust(trust) - var collect = Array() + var collect = [SecCertificate]() for cert in certs { collect.append(SecCertificateCreateWithData(nil,cert)!) } @@ -896,7 +982,7 @@ private class Security { for serverCert in serverCerts { for cert in certs { if cert == serverCert { - trustedCount++ + trustedCount += 1 break } } @@ -910,71 +996,74 @@ private class Security { } /** - Get the public key from a certificate data - - :param: data is the certificate to pull the public key from - - :returns: a public key - */ + Get the public key from a certificate data + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ func extractPublicKey(data: NSData) -> SecKeyRef? { - let possibleCert = SecCertificateCreateWithData(nil,data) - if let cert = possibleCert { - return extractPublicKeyFromCert(cert,policy: SecPolicyCreateBasicX509()) - } - return nil + guard let cert = SecCertificateCreateWithData(nil, data) else { return nil } + + return extractPublicKeyFromCert(cert, policy: SecPolicyCreateBasicX509()) } /** - Get the public key from a certificate - - :param: data is the certificate to pull the public key from - - :returns: a public key - */ + Get the public key from a certificate + + - parameter data: is the certificate to pull the public key from + + - returns: a public key + */ func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? { - let possibleTrust = UnsafeMutablePointer.alloc(1) - SecTrustCreateWithCertificates( cert, policy, possibleTrust) - if let trust = possibleTrust.memory { - var result: SecTrustResultType = 0 - SecTrustEvaluate(trust,&result) - return SecTrustCopyPublicKey(trust) - } - return nil + var possibleTrust: SecTrust? + SecTrustCreateWithCertificates(cert, policy, &possibleTrust) + + guard let trust = possibleTrust else { return nil } + + var result: SecTrustResultType = 0 + SecTrustEvaluate(trust, &result) + return SecTrustCopyPublicKey(trust) } /** - Get the certificate chain for the trust - - :param: trust is the trust to lookup the certificate chain for - - :returns: the certificate chain for the trust - */ - func certificateChainForTrust(trust: SecTrustRef) -> Array { - var collect = Array() - for var i = 0; i < SecTrustGetCertificateCount(trust); i++ { - let cert = SecTrustGetCertificateAtIndex(trust,i) - collect.append(SecCertificateCopyData(cert!)) + Get the certificate chain for the trust + + - parameter trust: is the trust to lookup the certificate chain for + + - returns: the certificate chain for the trust + */ + func certificateChainForTrust(trust: SecTrustRef) -> [NSData] { + let certificates = (0.. [NSData] in + var certificates = certificates + let cert = SecTrustGetCertificateAtIndex(trust, index) + certificates.append(SecCertificateCopyData(cert!)) + return certificates } - return collect + + return certificates } /** - Get the public key chain for the trust - - :param: trust is the trust to lookup the certificate chain and extract the public keys - - :returns: the public keys from the certifcate chain for the trust - */ - func publicKeyChainForTrust(trust: SecTrustRef) -> Array { - var collect = Array() + Get the public key chain for the trust + + - parameter trust: is the trust to lookup the certificate chain and extract the public keys + + - returns: the public keys from the certifcate chain for the trust + */ + func publicKeyChainForTrust(trust: SecTrustRef) -> [SecKeyRef] { let policy = SecPolicyCreateBasicX509() - for var i = 0; i < SecTrustGetCertificateCount(trust); i++ { - let cert = SecTrustGetCertificateAtIndex(trust,i) + let keys = (0.. [SecKeyRef] in + var keys = keys + let cert = SecTrustGetCertificateAtIndex(trust, index) if let key = extractPublicKeyFromCert(cert!, policy: policy) { - collect.append(key) + keys.append(key) } + + return keys } - return collect + + return keys } From ab529f8b93ed2cbcfa8a5bc07cd0168b60de1b33 Mon Sep 17 00:00:00 2001 From: Greg Crabtree Date: Thu, 12 May 2016 16:02:13 -0500 Subject: [PATCH 2/5] In progress... --- .DS_Store | Bin 0 -> 6148 bytes .../contents.xcworkspacedata | 116 +++++++++++------- .../UserInterfaceState.xcuserstate | Bin 0 -> 8355 bytes index.js | 7 +- 4 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 .DS_Store create mode 100644 RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8f5854c3fce8acc82951b8bc66e86ae8011f2988 GIT binary patch literal 6148 zcmeHK%}T>S5dOCIQ1Q~E;AQav#J)i&@!&OP4=6R$p8rBML7b700vaS-XYBoBG*Mb(zBI5F{C|S@Q67I)R=Uf8U_O`3>KkTwM}r5>r>{?=Vp3O)9_##c`<<#f&&$P} z@66ZTr@n<-K2ueff3#EcynecyeaxLdlQZB9I0M@(Tr&!&Xtl5v=(LcIRLn zN=SF-{xP{DWQuN`0cT*7fdicmrT$<1-v4g~`OFz`2L2TTp`RD|m`AeO+Buxm+DN^p piilsUxC-GQm16ozDL$qq;d+z-F*9rxxkB+DfkcBFXJDrcd;{jSN9q6o literal 0 HcmV?d00001 diff --git a/RNSwiftSocketIO.xcworkspace/contents.xcworkspacedata b/RNSwiftSocketIO.xcworkspace/contents.xcworkspacedata index 8b9f9d4..6c6a141 100644 --- a/RNSwiftSocketIO.xcworkspace/contents.xcworkspacedata +++ b/RNSwiftSocketIO.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -13,48 +86,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate b/RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..f68c66510b79d493cee62c8e6e3f48a7e9fa8f62 GIT binary patch literal 8355 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 literal 0 HcmV?d00001 diff --git a/index.js b/index.js index ebbe47d..fa81625 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,6 @@ 'use strict'; -var React = require('react-native'); - -var { - DeviceEventEmitter, - SocketIO -} = React; +import {DeviceEventEmitter, SocketIO } = require('react-native'); class Socket { constructor (host, config) { From 8c5d35ec94b97fed8d808d6576c2f2667cf4ba45 Mon Sep 17 00:00:00 2001 From: Greg Crabtree Date: Fri, 13 May 2016 15:24:22 -0500 Subject: [PATCH 3/5] * Upgraded dependencies to work with XCode 7+. * Incorporated new swift socket client source (release 6.1.1) * Fixed the apis that changed (close -> disconnect, joinnamespace now has a string to pass in) * Rewired Socket.swift to handle api changes. * Updated READ.ME for build notes --- README.md | 5 ++++- RNSwiftSocketIO/Socket.swift | 4 ++-- RNSwiftSocketIO/SocketBridge.m | 4 ++-- index.js | 22 +++++++++++----------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 10d9c8d..b4aee66 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,9 @@ $ npm install react-native-swift-socketio - Click your project in the navigator on the left and go to **build settings** - Search for **Objective-C Bridging Header** - Double click on the empty column -- Enter **node_modules/react-native-swift-socketio/RNSwiftSocketIO/SocketBridge.h** +- Enter **../node_modules/react-native-swift-socketio/RNSwiftSocketIO/SocketBridge.h** +- Search for **Header Search Paths** +- Double Click on the column (likely has other search paths in it already) +- Enter this text at the bottom of the column $(SRCROOT)/../node_modules/react-native-swift-socketio/RNSwiftSocketIO ... That should do it! Please let me know of any issues ... diff --git a/RNSwiftSocketIO/Socket.swift b/RNSwiftSocketIO/Socket.swift index 3e9b2d9..ef573c1 100644 --- a/RNSwiftSocketIO/Socket.swift +++ b/RNSwiftSocketIO/Socket.swift @@ -33,7 +33,7 @@ class SocketIO: NSObject { // Connect to socket with config self.socket = SocketIOClient( socketURL: self.connectionSocket, - opts:config as? [String : AnyObject] + options:config as? [String : AnyObject] ) // Initialise onAny events @@ -45,7 +45,7 @@ class SocketIO: NSObject { */ @objc func joinNamespace(namespace: String) -> Void { - self.socket.joinNamespace(namespace: namespace); + self.socket.joinNamespace(namespace); } /** diff --git a/RNSwiftSocketIO/SocketBridge.m b/RNSwiftSocketIO/SocketBridge.m index 835b71b..f491d84 100644 --- a/RNSwiftSocketIO/SocketBridge.m +++ b/RNSwiftSocketIO/SocketBridge.m @@ -13,10 +13,10 @@ @interface RCT_EXTERN_MODULE(SocketIO, NSObject) RCT_EXTERN_METHOD(initialise:(NSString*)connection config:(NSDictionary*)config) RCT_EXTERN_METHOD(addHandlers:(NSDictionary*)handlers) RCT_EXTERN_METHOD(connect) -RCT_EXTERN_METHOD(close:(BOOL)fast) +RCT_EXTERN_METHOD(disconnect) RCT_EXTERN_METHOD(reconnect) RCT_EXTERN_METHOD(emit:(NSString*)event items:(id)items) -RCT_EXTERN_METHOD(joinNamespace) +RCT_EXTERN_METHOD(joinNamespace:(NSString *)namespace) RCT_EXTERN_METHOD(leaveNamespace) @end diff --git a/index.js b/index.js index fa81625..f2ff775 100644 --- a/index.js +++ b/index.js @@ -1,13 +1,14 @@ 'use strict'; -import {DeviceEventEmitter, SocketIO } = require('react-native'); +import { DeviceEventEmitter, NativeModules } from 'react-native'; +let SocketIO = NativeModules.SocketIO; class Socket { constructor (host, config) { - if(typeof host === 'undefined') + if (typeof host === 'undefined') throw 'Hello there! Could you please give socket a host, please.'; - if(typeof config === 'undefined') + if (typeof config === 'undefined') config = {}; this.sockets = SocketIO; @@ -35,15 +36,15 @@ class Socket { } _handleEvent (event) { - if(this.handlers.hasOwnProperty(event.name)) + if (this.handlers.hasOwnProperty(event.name)) this.handlers[event.name]( (event.hasOwnProperty('items')) ? event.items : null ); - if(this.defaultHandlers.hasOwnProperty(event.name)) + if (this.defaultHandlers.hasOwnProperty(event.name)) this.defaultHandlers[event.name](); - if(this.onAnyHandler) this.onAnyHandler(event); + if (this.onAnyHandler) this.onAnyHandler(event); } connect () { @@ -62,17 +63,16 @@ class Socket { this.sockets.emit(event, data); } - joinNamespace () { - this.sockets.joinNamespace(); + joinNamespace (namespace) { + this.sockets.joinNamespace(namespace); } leaveNamespace () { this.sockets.leaveNamespace(); } - close (fast) { - if(typeof fast === 'undefined') fast = false; - this.sockets.close(fast); + disconnect () { + this.sockets.close(); } reconnect () { From b194faad6a066cb5855ac15ed3337d3ae5679775 Mon Sep 17 00:00:00 2001 From: Greg Crabtree Date: Fri, 13 May 2016 15:28:37 -0500 Subject: [PATCH 4/5] Improved .gitignore --- .gitignore | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 07e4fe7..5768284 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,42 @@ -node_modules/**/* +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.idea +.gradle +local.properties +*.apk +*.ap_ +*.class +*.iml +*.log +gen/ + +# node.js +# +node_modules/ +npm-debug.log +coverage/ +docs/ From 01e4d60957d1be9fd154f9e5bce47967572995d2 Mon Sep 17 00:00:00 2001 From: Greg Crabtree Date: Fri, 13 May 2016 15:37:40 -0500 Subject: [PATCH 5/5] Force project to respect new .gitignore. --- .DS_Store | Bin 6148 -> 0 bytes .../xcshareddata/RNSwiftSocketIO.xccheckout | 41 ------------------ .../UserInterfaceState.xcuserstate | Bin 8355 -> 0 bytes RNSwiftSocketIO/SocketIOClient/.DS_Store | Bin 6148 -> 0 bytes 4 files changed, 41 deletions(-) delete mode 100644 .DS_Store delete mode 100644 RNSwiftSocketIO.xcworkspace/xcshareddata/RNSwiftSocketIO.xccheckout delete mode 100644 RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 RNSwiftSocketIO/SocketIOClient/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8f5854c3fce8acc82951b8bc66e86ae8011f2988..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5dOCIQ1Q~E;AQav#J)i&@!&OP4=6R$p8rBML7b700vaS-XYBoBG*Mb(zBI5F{C|S@Q67I)R=Uf8U_O`3>KkTwM}r5>r>{?=Vp3O)9_##c`<<#f&&$P} z@66ZTr@n<-K2ueff3#EcynecyeaxLdlQZB9I0M@(Tr&!&Xtl5v=(LcIRLn zN=SF-{xP{DWQuN`0cT*7fdicmrT$<1-v4g~`OFz`2L2TTp`RD|m`AeO+Buxm+DN^p piilsUxC-GQm16ozDL$qq;d+z-F*9rxxkB+DfkcBFXJDrcd;{jSN9q6o diff --git a/RNSwiftSocketIO.xcworkspace/xcshareddata/RNSwiftSocketIO.xccheckout b/RNSwiftSocketIO.xcworkspace/xcshareddata/RNSwiftSocketIO.xccheckout deleted file mode 100644 index 34e0d2d..0000000 --- a/RNSwiftSocketIO.xcworkspace/xcshareddata/RNSwiftSocketIO.xccheckout +++ /dev/null @@ -1,41 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - 56A17F65-50DD-45A0-B28B-9D72D5AA20F6 - IDESourceControlProjectName - RNSwiftSocketIO - IDESourceControlProjectOriginsDictionary - - C78D17F9D1170DAA4E77387A3C7E2A6009777A0D - https://github.com/kirkness/react-native-swift-socketio.git - - IDESourceControlProjectPath - RNSwiftSocketIO.xcworkspace - IDESourceControlProjectRelativeInstallPathDictionary - - C78D17F9D1170DAA4E77387A3C7E2A6009777A0D - .. - - IDESourceControlProjectURL - https://github.com/kirkness/react-native-swift-socketio.git - IDESourceControlProjectVersion - 111 - IDESourceControlProjectWCCIdentifier - C78D17F9D1170DAA4E77387A3C7E2A6009777A0D - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - C78D17F9D1170DAA4E77387A3C7E2A6009777A0D - IDESourceControlWCCName - RNSwiftSocketIO - - - - diff --git a/RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate b/RNSwiftSocketIO.xcworkspace/xcuserdata/Greg.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index f68c66510b79d493cee62c8e6e3f48a7e9fa8f62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8355 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 diff --git a/RNSwiftSocketIO/SocketIOClient/.DS_Store b/RNSwiftSocketIO/SocketIOClient/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0