├── .gitignore ├── README.md ├── RNSwiftSocketIO.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── RNSwiftSocketIO.xccheckout ├── RNSwiftSocketIO ├── Socket.swift ├── SocketBridge.h ├── SocketBridge.m └── SocketIOClient │ ├── SocketAckEmitter.swift │ ├── SocketAckManager.swift │ ├── SocketAnyEvent.swift │ ├── SocketEngine.swift │ ├── SocketEngineClient.swift │ ├── SocketEventHandler.swift │ ├── SocketFixUTF8.swift │ ├── SocketIOClient.swift │ ├── SocketIOClientStatus.swift │ ├── SocketLogger.swift │ ├── SocketPacket.swift │ ├── SocketParser.swift │ ├── SocketStringReader.swift │ ├── SocketTypes.swift │ ├── SwiftRegex.swift │ └── WebSocket.swift ├── examples └── index.ios.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🚨this library is not maintained anymore! 2 | 3 | # A React Native wrapper for the Socket.io Swift Library 4 | 5 | The wrapped 'Socket.IO-Client-Swift' can be [found here](https://github.com/socketio/socket.io-client-swift). 6 | 7 | ### Example 8 | I've also added a super simple example app to /examples, copy and paste to your index.ios.js. 9 | ``` js 10 | /** 11 | * Pass in an optional config obj, this can include most of the 12 | * standard props supported by the swift library 13 | */ 14 | var socketConfig = { path: '/socket' }; 15 | var socket = new SocketIO('localhost:3000', socketConfig); 16 | 17 | // Connect! 18 | socket.connect(); 19 | 20 | // An event to be fired on connection to socket 21 | socket.on('connect', () => { 22 | console.log('Wahey -> connected!'); 23 | }); 24 | 25 | // Event called when 'someEvent' it emitted by server 26 | socket.on('someEvent', (data) => { 27 | console.log('Some event was called, check out this data: ', data); 28 | }); 29 | 30 | // Called when anything is emitted by the server 31 | socket.onAny((event) => { 32 | console.log(`${event.name} was called with data: `, event.items); 33 | }); 34 | 35 | // Manually join namespace 36 | socket.joinNamespace() 37 | 38 | // Leave namespace, back to '/' 39 | socket.leaveNamespace() 40 | 41 | // Emit an event to server 42 | socket.emit('helloWorld', {some: 'data'}); 43 | 44 | // Optional boolean param 'fast' - defaults to false 45 | socket.close(true); 46 | 47 | // Reconnect to a closed socket 48 | socket.reconect(); 49 | ``` 50 | 51 | ### Constructor 52 | 53 | Requires: 54 | `host` - example: 'localhost:3000' 55 | 56 | Optional: 57 | `config` - JSON object comprising any of the options listed below. 58 | 59 | 60 | ### Config 61 | 62 | - `connectParams: Any Object` - Any data to be sent with the connection. 63 | - `reconnects: Boolean` Default is `true` 64 | - `reconnectAttempts: Int` Default is `-1` (infinite tries) 65 | - `reconnectWait: Number` Default is `10` 66 | - `forcePolling: Boolean` Default is `false`. `true` forces the client to use xhr-polling. 67 | - `forceWebsockets: Boolean` Default is `false`. `true` forces the client to use WebSockets. 68 | - `nsp: String` Default is `"/"`. Connects to a namespace. 69 | - `log: Bool` If `true` socket will log debug messages. Default is false. 70 | - `path: String` - If the server uses a custom path. ex: `"/swift"`. Default is `""` 71 | 72 | #### Not supported yet but easy enough to implement. 73 | 74 | - ~~`cookies: [NSHTTPCookie]?` An array of NSHTTPCookies. Passed during the handshake. Default is nil.~~ 75 | - ~~`sessionDelegate: NSURLSessionDelegate` Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs. Default is nil.~~ 76 | 77 | ### Methods 78 | 79 | - `connect` - Connect to socket 80 | - `on` - Add event handler to socket 81 | - `@param` - String - event name 82 | - `@param` - Function - callback 83 | - `onAny` - Add event handler to any event 84 | - `@param` - Function - callback 85 | - `emit` - Emit an event to server 86 | - `@param` - String - Event name 87 | - `@param` - Anything - Data to be sent 88 | - `close` - Close the connection 89 | - `@param` - Boolean - should close fast? 90 | - `reconnect` - Reconnect to a closed connection 91 | - `joinNamespace` - Manually join namespace 92 | - `leaveNamespace` - Leave namespace, back to '/' 93 | 94 | ### Known issues 95 | 96 | - Would rather this in an xcode framework but run into non-modular header issue. 97 | 98 | ### Install 99 | 100 | - Run in your project: 101 | ```sh 102 | $ npm install react-native-swift-socketio 103 | ``` 104 | 105 | - Open up your project in xcode and right click the package. 106 | - Click **Add files to 'Your project name'** 107 | - Navigate to **/node_modules/react-native-swift-socketio/RNSwiftSocketIO** 108 | - Click 'Add' 109 | - Click your project in the navigator on the left and go to **build settings** 110 | - Search for **Objective-C Bridging Header** 111 | - Double click on the empty column 112 | - Enter **node_modules/react-native-swift-socketio/RNSwiftSocketIO/SocketBridge.h** 113 | 114 | ... That should do it! Please let me know of any issues ... 115 | -------------------------------------------------------------------------------- /RNSwiftSocketIO.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 19 | 21 | 22 | 24 | 25 | 27 | 28 | 30 | 31 | 33 | 34 | 36 | 37 | 39 | 40 | 42 | 43 | 45 | 46 | 48 | 49 | 51 | 52 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /RNSwiftSocketIO.xcworkspace/xcshareddata/RNSwiftSocketIO.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 56A17F65-50DD-45A0-B28B-9D72D5AA20F6 9 | IDESourceControlProjectName 10 | RNSwiftSocketIO 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C78D17F9D1170DAA4E77387A3C7E2A6009777A0D 14 | https://github.com/kirkness/react-native-swift-socketio.git 15 | 16 | IDESourceControlProjectPath 17 | RNSwiftSocketIO.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C78D17F9D1170DAA4E77387A3C7E2A6009777A0D 21 | .. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/kirkness/react-native-swift-socketio.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C78D17F9D1170DAA4E77387A3C7E2A6009777A0D 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C78D17F9D1170DAA4E77387A3C7E2A6009777A0D 36 | IDESourceControlWCCName 37 | RNSwiftSocketIO 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/Socket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Socket.swift 3 | // ReactSockets 4 | // 5 | // Created by Henry Kirkness on 10/05/2015. 6 | // Copyright (c) 2015 Facebook. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc(SocketIO) 12 | class SocketIO: NSObject { 13 | 14 | var socket: SocketIOClient! 15 | var connectionSocket: String! 16 | var bridge: RCTBridge! 17 | 18 | /** 19 | * Construct and expose RCTBridge to module 20 | */ 21 | 22 | @objc func initWithBridge(_bridge: RCTBridge) { 23 | self.bridge = _bridge 24 | } 25 | 26 | /** 27 | * Initialise and configure socket 28 | */ 29 | 30 | @objc func initialise(connection: String, config: NSDictionary) -> Void { 31 | connectionSocket = connection 32 | 33 | // Connect to socket with config 34 | self.socket = SocketIOClient( 35 | socketURL: self.connectionSocket, 36 | opts:config as? [String : AnyObject] 37 | ) 38 | 39 | // Initialise onAny events 40 | self.onAnyEvent() 41 | } 42 | 43 | /** 44 | * Manually join the namespace 45 | */ 46 | 47 | @objc func joinNamespace() { 48 | self.socket.joinNamespace(); 49 | } 50 | 51 | /** 52 | * Leave namespace back to '/' 53 | */ 54 | 55 | @objc func leaveNamespace() { 56 | self.socket.leaveNamespace(); 57 | } 58 | 59 | /** 60 | * Exposed but not currently used 61 | * add NSDictionary of handler events 62 | */ 63 | 64 | @objc func addHandlers(handlers: NSDictionary) -> Void { 65 | for handler in handlers { 66 | self.socket.on(handler.key as! String) { data, ack in 67 | self.bridge.eventDispatcher.sendDeviceEventWithName( 68 | "socketEvent", body: handler.key as! String) 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Emit event to server 75 | */ 76 | 77 | @objc func emit(event: String, items: AnyObject) -> Void { 78 | self.socket.emit(event, items) 79 | } 80 | 81 | /** 82 | * PRIVATE: handler called on any event 83 | */ 84 | 85 | private func onAnyEventHandler (sock: SocketAnyEvent) -> Void { 86 | if let items = sock.items { 87 | self.bridge.eventDispatcher.sendDeviceEventWithName("socketEvent", 88 | body: ["name": sock.event, "items": items]) 89 | } else { 90 | self.bridge.eventDispatcher.sendDeviceEventWithName("socketEvent", 91 | body: ["name": sock.event]) 92 | } 93 | } 94 | 95 | /** 96 | * Trigger the event above on any event 97 | * Currently adding handlers to event on the JS layer 98 | */ 99 | 100 | @objc func onAnyEvent() -> Void { 101 | self.socket.onAny(self.onAnyEventHandler) 102 | } 103 | 104 | // Connect to socket 105 | @objc func connect() -> Void { 106 | self.socket.connect() 107 | } 108 | 109 | // Reconnect to socket 110 | @objc func reconnect() -> Void { 111 | self.socket.reconnect() 112 | } 113 | 114 | // Disconnect from socket 115 | @objc func close(fast: Bool) -> Void { 116 | self.socket.close(fast: fast) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketBridge.h: -------------------------------------------------------------------------------- 1 | // 2 | // SocketBridge.h 3 | // RNSwiftSocketIO 4 | // 5 | // Created by Henry Kirkness on 12/05/2015. 6 | // Copyright (c) 2015 Facebook. All rights reserved. 7 | // 8 | 9 | #ifndef RNSwiftSocketIO_SocketBridge_h 10 | #define RNSwiftSocketIO_SocketBridge_h 11 | 12 | #import "RCTBridge.h" 13 | #import "RCTBridgeModule.h" 14 | #import "RCTEventDispatcher.h" 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketBridge.m: -------------------------------------------------------------------------------- 1 | // 2 | // SocketBridge.m 3 | // RNSwiftSocketIO 4 | // 5 | // Created by Henry Kirkness on 10/05/2015. 6 | // Copyright (c) 2015 Facebook. All rights reserved. 7 | // 8 | 9 | #import "RCTBridge.h" 10 | 11 | @interface RCT_EXTERN_MODULE(SocketIO, NSObject) 12 | 13 | RCT_EXTERN_METHOD(initialise:(NSString*)connection config:(NSDictionary*)config) 14 | RCT_EXTERN_METHOD(addHandlers:(NSDictionary*)handlers) 15 | RCT_EXTERN_METHOD(connect) 16 | RCT_EXTERN_METHOD(close:(BOOL)fast) 17 | RCT_EXTERN_METHOD(reconnect) 18 | RCT_EXTERN_METHOD(emit:(NSString*)event items:(id)items) 19 | RCT_EXTERN_METHOD(joinNamespace) 20 | RCT_EXTERN_METHOD(leaveNamespace) 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketAckEmitter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckEmitter.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 9/16/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketAckEmitter: NSObject { 28 | let socket: SocketIOClient 29 | let ackNum: Int 30 | 31 | init(socket: SocketIOClient, ackNum: Int) { 32 | self.socket = socket 33 | self.ackNum = ackNum 34 | } 35 | 36 | public func with(items: AnyObject...) { 37 | socket.emitAck(ackNum, withItems: items) 38 | } 39 | 40 | public func with(items: [AnyObject]) { 41 | socket.emitAck(ackNum, withItems: items) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketAckManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckManager.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/3/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | private struct SocketAck: Hashable, Equatable { 28 | let ack: Int 29 | var callback: AckCallback! 30 | var hashValue: Int { 31 | return ack.hashValue 32 | } 33 | 34 | init(ack: Int) { 35 | self.ack = ack 36 | } 37 | 38 | init(ack: Int, callback: AckCallback) { 39 | self.ack = ack 40 | self.callback = callback 41 | } 42 | } 43 | 44 | private func <(lhs: SocketAck, rhs: SocketAck) -> Bool { 45 | return lhs.ack < rhs.ack 46 | } 47 | 48 | private func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { 49 | return lhs.ack == rhs.ack 50 | } 51 | 52 | struct SocketAckManager { 53 | private var acks = Set(minimumCapacity: 1) 54 | 55 | mutating func addAck(ack: Int, callback: AckCallback) { 56 | acks.insert(SocketAck(ack: ack, callback: callback)) 57 | } 58 | 59 | mutating func executeAck(ack: Int, items: [AnyObject]) { 60 | let callback = acks.remove(SocketAck(ack: ack)) 61 | 62 | dispatch_async(dispatch_get_main_queue()) { 63 | callback?.callback(items) 64 | } 65 | } 66 | 67 | mutating func timeoutAck(ack: Int) { 68 | let callback = acks.remove(SocketAck(ack: ack)) 69 | 70 | dispatch_async(dispatch_get_main_queue()) { 71 | callback?.callback(["NO ACK"]) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketAnyEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAnyEvent.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/28/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | @objc public final class SocketAnyEvent: NSObject { 28 | public let event: String! 29 | public let items: NSArray? 30 | override public var description: String { 31 | return "SocketAnyEvent: Event: \(event) items: \(items ?? nil)" 32 | } 33 | 34 | init(event: String, items: NSArray?) { 35 | self.event = event 36 | self.items = items 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketEngine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngine.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/3/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketEngine: NSObject, WebSocketDelegate { 28 | private typealias Probe = (msg: String, type: PacketType, data: [NSData]?) 29 | private typealias ProbeWaitQueue = [Probe] 30 | 31 | private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet 32 | private let emitQueue = dispatch_queue_create("engineEmitQueue", DISPATCH_QUEUE_SERIAL) 33 | private let handleQueue = dispatch_queue_create("engineHandleQueue", DISPATCH_QUEUE_SERIAL) 34 | private let logType = "SocketEngine" 35 | private let parseQueue = dispatch_queue_create("engineParseQueue", DISPATCH_QUEUE_SERIAL) 36 | private let session: NSURLSession! 37 | private let workQueue = NSOperationQueue() 38 | 39 | private var closed = false 40 | private var extraHeaders: [String: String]? 41 | private var fastUpgrade = false 42 | private var forcePolling = false 43 | private var forceWebsockets = false 44 | private var pingInterval: Double? 45 | private var pingTimer: NSTimer? 46 | private var pingTimeout = 0.0 { 47 | didSet { 48 | pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25)) 49 | } 50 | } 51 | private var pongsMissed = 0 52 | private var pongsMissedMax = 0 53 | private var postWait = [String]() 54 | private var probing = false 55 | private var probeWait = ProbeWaitQueue() 56 | private var waitingForPoll = false 57 | private var waitingForPost = false 58 | private var websocketConnected = false 59 | 60 | private(set) var connected = false 61 | private(set) var polling = true 62 | private(set) var websocket = false 63 | 64 | weak var client: SocketEngineClient? 65 | var cookies: [NSHTTPCookie]? 66 | var sid = "" 67 | var socketPath = "" 68 | var urlPolling: String? 69 | var urlWebSocket: String? 70 | var ws: WebSocket? 71 | 72 | @objc public enum PacketType: Int { 73 | case Open, Close, Ping, Pong, Message, Upgrade, Noop 74 | 75 | init?(str: String) { 76 | if let value = Int(str), raw = PacketType(rawValue: value) { 77 | self = raw 78 | } else { 79 | return nil 80 | } 81 | } 82 | } 83 | 84 | public init(client: SocketEngineClient, sessionDelegate: NSURLSessionDelegate?) { 85 | self.client = client 86 | self.session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), 87 | delegate: sessionDelegate, delegateQueue: workQueue) 88 | } 89 | 90 | public convenience init(client: SocketEngineClient, opts: NSDictionary?) { 91 | self.init(client: client, sessionDelegate: opts?["sessionDelegate"] as? NSURLSessionDelegate) 92 | forceWebsockets = opts?["forceWebsockets"] as? Bool ?? false 93 | forcePolling = opts?["forcePolling"] as? Bool ?? false 94 | cookies = opts?["cookies"] as? [NSHTTPCookie] 95 | socketPath = opts?["path"] as? String ?? "" 96 | extraHeaders = opts?["extraHeaders"] as? [String: String] 97 | } 98 | 99 | deinit { 100 | Logger.log("Engine is being deinit", type: logType) 101 | } 102 | 103 | private func checkIfMessageIsBase64Binary(var message: String) { 104 | if message.hasPrefix("b4") { 105 | // binary in base64 string 106 | message.removeRange(Range(start: message.startIndex, 107 | end: message.startIndex.advancedBy(2))) 108 | 109 | if let data = NSData(base64EncodedString: message, 110 | options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { 111 | client?.parseBinaryData(data) 112 | } 113 | } 114 | } 115 | 116 | public func close(fast fast: Bool) { 117 | Logger.log("Engine is being closed. Fast: %@", type: logType, args: fast) 118 | 119 | pingTimer?.invalidate() 120 | closed = true 121 | 122 | ws?.disconnect() 123 | 124 | if fast || polling { 125 | write("", withType: PacketType.Close, withData: nil) 126 | client?.engineDidClose("Disconnect") 127 | } 128 | 129 | stopPolling() 130 | } 131 | 132 | private func createBinaryDataForSend(data: NSData) -> (NSData?, String?) { 133 | if websocket { 134 | var byteArray = [UInt8](count: 1, repeatedValue: 0x0) 135 | byteArray[0] = 4 136 | let mutData = NSMutableData(bytes: &byteArray, length: 1) 137 | 138 | mutData.appendData(data) 139 | 140 | return (mutData, nil) 141 | } else { 142 | var str = "b4" 143 | str += data.base64EncodedStringWithOptions( 144 | NSDataBase64EncodingOptions.Encoding64CharacterLineLength) 145 | 146 | return (nil, str) 147 | } 148 | } 149 | 150 | private func createURLs(params: [String: AnyObject]?) -> (String?, String?) { 151 | if client == nil { 152 | return (nil, nil) 153 | } 154 | 155 | let path = socketPath == "" ? "/socket.io" : socketPath 156 | 157 | let url = "\(client!.socketURL)\(path)/?transport=" 158 | var urlPolling: String 159 | var urlWebSocket: String 160 | 161 | if client!.secure { 162 | urlPolling = "https://" + url + "polling" 163 | urlWebSocket = "wss://" + url + "websocket" 164 | } else { 165 | urlPolling = "http://" + url + "polling" 166 | urlWebSocket = "ws://" + url + "websocket" 167 | } 168 | 169 | if params != nil { 170 | for (key, value) in params! { 171 | let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( 172 | allowedCharacterSet)! 173 | urlPolling += "&\(keyEsc)=" 174 | urlWebSocket += "&\(keyEsc)=" 175 | 176 | if value is String { 177 | let valueEsc = (value as! String).stringByAddingPercentEncodingWithAllowedCharacters( 178 | allowedCharacterSet)! 179 | urlPolling += "\(valueEsc)" 180 | urlWebSocket += "\(valueEsc)" 181 | } else { 182 | urlPolling += "\(value)" 183 | urlWebSocket += "\(value)" 184 | } 185 | } 186 | } 187 | 188 | return (urlPolling, urlWebSocket) 189 | } 190 | 191 | private func createWebsocket(andConnect connect: Bool) { 192 | let wsUrl = urlWebSocket! + (sid == "" ? "" : "&sid=\(sid)") 193 | 194 | ws = WebSocket(url: NSURL(string: wsUrl)!, 195 | cookies: cookies) 196 | 197 | if extraHeaders != nil { 198 | for (headerName, value) in extraHeaders! { 199 | ws?.headers[headerName] = value 200 | } 201 | } 202 | 203 | ws?.queue = handleQueue 204 | ws?.delegate = self 205 | 206 | if connect { 207 | ws?.connect() 208 | } 209 | } 210 | 211 | private func doFastUpgrade() { 212 | if waitingForPoll { 213 | Logger.error("Outstanding poll when switched to WebSockets," + 214 | "we'll probably disconnect soon. You should report this.", type: logType) 215 | } 216 | 217 | sendWebSocketMessage("", withType: PacketType.Upgrade, datas: nil) 218 | websocket = true 219 | polling = false 220 | fastUpgrade = false 221 | probing = false 222 | flushProbeWait() 223 | } 224 | 225 | private func doPoll() { 226 | if websocket || waitingForPoll || !connected { 227 | return 228 | } 229 | 230 | waitingForPoll = true 231 | let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)&b64=1")!) 232 | 233 | if cookies != nil { 234 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) 235 | req.allHTTPHeaderFields = headers 236 | } 237 | 238 | if extraHeaders != nil { 239 | for (headerName, value) in extraHeaders! { 240 | req.setValue(value, forHTTPHeaderField: headerName) 241 | } 242 | } 243 | 244 | doRequest(req) 245 | } 246 | 247 | private func doRequest(req: NSMutableURLRequest) { 248 | if !polling { 249 | return 250 | } 251 | 252 | req.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData 253 | 254 | Logger.log("Doing polling request", type: logType) 255 | 256 | session.dataTaskWithRequest(req) {[weak self] data, res, err in 257 | if let this = self { 258 | if err != nil || data == nil { 259 | if this.polling { 260 | this.handlePollingFailed(err?.localizedDescription ?? "Error") 261 | } else { 262 | Logger.error(err?.localizedDescription ?? "Error", type: this.logType) 263 | } 264 | return 265 | } 266 | 267 | Logger.log("Got polling response", type: this.logType) 268 | 269 | if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) as? String { 270 | dispatch_async(this.parseQueue) {[weak this] in 271 | this?.parsePollingMessage(str) 272 | } 273 | } 274 | 275 | this.waitingForPoll = false 276 | 277 | if this.fastUpgrade { 278 | this.doFastUpgrade() 279 | } else if !this.closed && this.polling { 280 | this.doPoll() 281 | } 282 | }}.resume() 283 | } 284 | 285 | private func flushProbeWait() { 286 | Logger.log("Flushing probe wait", type: logType) 287 | 288 | dispatch_async(emitQueue) {[weak self] in 289 | if let this = self { 290 | for waiter in this.probeWait { 291 | this.write(waiter.msg, withType: waiter.type, withData: waiter.data) 292 | } 293 | 294 | this.probeWait.removeAll(keepCapacity: false) 295 | 296 | if this.postWait.count != 0 { 297 | this.flushWaitingForPostToWebSocket() 298 | } 299 | } 300 | } 301 | } 302 | 303 | private func flushWaitingForPost() { 304 | if postWait.count == 0 || !connected { 305 | return 306 | } else if websocket { 307 | flushWaitingForPostToWebSocket() 308 | return 309 | } 310 | 311 | var postStr = "" 312 | 313 | for packet in postWait { 314 | let len = packet.characters.count 315 | 316 | postStr += "\(len):\(packet)" 317 | } 318 | 319 | postWait.removeAll(keepCapacity: false) 320 | 321 | let req = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&sid=\(sid)")!) 322 | 323 | if let cookies = cookies { 324 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies) 325 | req.allHTTPHeaderFields = headers 326 | } 327 | 328 | req.HTTPMethod = "POST" 329 | req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") 330 | 331 | let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, 332 | allowLossyConversion: false)! 333 | 334 | req.HTTPBody = postData 335 | req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") 336 | 337 | waitingForPost = true 338 | 339 | Logger.log("POSTing: %@", type: logType, args: postStr) 340 | 341 | session.dataTaskWithRequest(req) {[weak self] data, res, err in 342 | if let this = self { 343 | if err != nil && this.polling { 344 | this.handlePollingFailed(err?.localizedDescription ?? "Error") 345 | return 346 | } else if err != nil { 347 | NSLog(err?.localizedDescription ?? "Error") 348 | return 349 | } 350 | 351 | this.waitingForPost = false 352 | 353 | dispatch_async(this.emitQueue) {[weak this] in 354 | if !(this?.fastUpgrade ?? true) { 355 | this?.flushWaitingForPost() 356 | this?.doPoll() 357 | } 358 | } 359 | }}.resume() 360 | } 361 | 362 | // We had packets waiting for send when we upgraded 363 | // Send them raw 364 | private func flushWaitingForPostToWebSocket() { 365 | guard let ws = self.ws else {return} 366 | 367 | for msg in postWait { 368 | ws.writeString(msg) 369 | } 370 | 371 | postWait.removeAll(keepCapacity: true) 372 | } 373 | 374 | private func handleClose() { 375 | if let client = client where polling == true { 376 | client.engineDidClose("Disconnect") 377 | } 378 | } 379 | 380 | private func handleMessage(message: String) { 381 | client?.parseSocketMessage(message) 382 | } 383 | 384 | private func handleNOOP() { 385 | doPoll() 386 | } 387 | 388 | private func handleOpen(openData: String) { 389 | let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! 390 | do { 391 | let json = try NSJSONSerialization.JSONObjectWithData(mesData, 392 | options: NSJSONReadingOptions.AllowFragments) as? NSDictionary 393 | if let sid = json?["sid"] as? String { 394 | let upgradeWs: Bool 395 | 396 | self.sid = sid 397 | connected = true 398 | 399 | if let upgrades = json?["upgrades"] as? [String] { 400 | upgradeWs = upgrades.filter {$0 == "websocket"}.count != 0 401 | } else { 402 | upgradeWs = false 403 | } 404 | 405 | if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double { 406 | self.pingInterval = pingInterval / 1000.0 407 | self.pingTimeout = pingTimeout / 1000.0 408 | } 409 | 410 | if !forcePolling && !forceWebsockets && upgradeWs { 411 | createWebsocket(andConnect: true) 412 | } 413 | } 414 | } catch { 415 | Logger.error("Error parsing open packet", type: logType) 416 | return 417 | } 418 | 419 | startPingTimer() 420 | 421 | if !forceWebsockets { 422 | doPoll() 423 | } 424 | } 425 | 426 | private func handlePong(pongMessage: String) { 427 | pongsMissed = 0 428 | 429 | // We should upgrade 430 | if pongMessage == "3probe" { 431 | upgradeTransport() 432 | } 433 | } 434 | 435 | // A poll failed, tell the client about it 436 | private func handlePollingFailed(reason: String) { 437 | connected = false 438 | ws?.disconnect() 439 | pingTimer?.invalidate() 440 | waitingForPoll = false 441 | waitingForPost = false 442 | 443 | if !closed { 444 | client?.didError(reason) 445 | client?.engineDidClose(reason) 446 | } 447 | } 448 | 449 | public func open(opts: [String: AnyObject]? = nil) { 450 | if connected { 451 | Logger.error("Tried to open while connected", type: logType) 452 | client?.didError("Tried to open while connected") 453 | 454 | return 455 | } 456 | 457 | Logger.log("Starting engine", type: logType) 458 | Logger.log("Handshaking", type: logType) 459 | 460 | closed = false 461 | 462 | (urlPolling, urlWebSocket) = createURLs(opts) 463 | 464 | if forceWebsockets { 465 | polling = false 466 | websocket = true 467 | createWebsocket(andConnect: true) 468 | return 469 | } 470 | 471 | let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling! + "&b64=1")!) 472 | 473 | if cookies != nil { 474 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) 475 | reqPolling.allHTTPHeaderFields = headers 476 | } 477 | 478 | if let extraHeaders = extraHeaders { 479 | for (headerName, value) in extraHeaders { 480 | reqPolling.setValue(value, forHTTPHeaderField: headerName) 481 | } 482 | } 483 | 484 | doRequest(reqPolling) 485 | } 486 | 487 | private func parsePollingMessage(str: String) { 488 | guard str.characters.count != 1 else { 489 | return 490 | } 491 | 492 | var reader = SocketStringReader(message: str) 493 | 494 | while reader.hasNext { 495 | if let n = Int(reader.readUntilStringOccurence(":")) { 496 | let str = reader.read(n) 497 | 498 | dispatch_async(handleQueue) { 499 | self.parseEngineMessage(str, fromPolling: true) 500 | } 501 | } else { 502 | dispatch_async(handleQueue) { 503 | self.parseEngineMessage(str, fromPolling: true) 504 | } 505 | break 506 | } 507 | } 508 | } 509 | 510 | private func parseEngineData(data: NSData) { 511 | Logger.log("Got binary data: %@", type: "SocketEngine", args: data) 512 | client?.parseBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1))) 513 | } 514 | 515 | private func parseEngineMessage(var message: String, fromPolling: Bool) { 516 | Logger.log("Got message: %@", type: logType, args: message) 517 | 518 | if fromPolling { 519 | fixDoubleUTF8(&message) 520 | } 521 | 522 | let type = PacketType(str: (message["^(\\d)"].groups()?[1]) ?? "") ?? { 523 | self.checkIfMessageIsBase64Binary(message) 524 | return .Noop 525 | }() 526 | 527 | switch type { 528 | case PacketType.Message: 529 | message.removeAtIndex(message.startIndex) 530 | handleMessage(message) 531 | case PacketType.Noop: 532 | handleNOOP() 533 | case PacketType.Pong: 534 | handlePong(message) 535 | case PacketType.Open: 536 | message.removeAtIndex(message.startIndex) 537 | handleOpen(message) 538 | case PacketType.Close: 539 | handleClose() 540 | default: 541 | Logger.log("Got unknown packet type", type: logType) 542 | } 543 | } 544 | 545 | private func probeWebSocket() { 546 | if websocketConnected { 547 | sendWebSocketMessage("probe", withType: PacketType.Ping) 548 | } 549 | } 550 | 551 | /// Send an engine message (4) 552 | public func send(msg: String, withData datas: [NSData]?) { 553 | if probing { 554 | probeWait.append((msg, PacketType.Message, datas)) 555 | } else { 556 | write(msg, withType: PacketType.Message, withData: datas) 557 | } 558 | } 559 | 560 | @objc private func sendPing() { 561 | //Server is not responding 562 | if pongsMissed > pongsMissedMax { 563 | pingTimer?.invalidate() 564 | client?.engineDidClose("Ping timeout") 565 | return 566 | } 567 | 568 | ++pongsMissed 569 | write("", withType: PacketType.Ping, withData: nil) 570 | } 571 | 572 | /// Send polling message. 573 | /// Only call on emitQueue 574 | private func sendPollMessage(var msg: String, withType type: PacketType, 575 | datas:[NSData]? = nil) { 576 | Logger.log("Sending poll: %@ as type: %@", type: logType, args: msg, type.rawValue) 577 | 578 | doubleEncodeUTF8(&msg) 579 | let strMsg = "\(type.rawValue)\(msg)" 580 | 581 | postWait.append(strMsg) 582 | 583 | for data in datas ?? [] { 584 | let (_, b64Data) = createBinaryDataForSend(data) 585 | 586 | postWait.append(b64Data!) 587 | } 588 | 589 | if !waitingForPost { 590 | flushWaitingForPost() 591 | } 592 | } 593 | 594 | /// Send message on WebSockets 595 | /// Only call on emitQueue 596 | private func sendWebSocketMessage(str: String, withType type: PacketType, 597 | datas:[NSData]? = nil) { 598 | Logger.log("Sending ws: %@ as type: %@", type: logType, args: str, type.rawValue) 599 | 600 | ws?.writeString("\(type.rawValue)\(str)") 601 | 602 | for data in datas ?? [] { 603 | let (data, _) = createBinaryDataForSend(data) 604 | if data != nil { 605 | ws?.writeData(data!) 606 | } 607 | } 608 | } 609 | 610 | // Starts the ping timer 611 | private func startPingTimer() { 612 | if let pingInterval = pingInterval { 613 | pingTimer?.invalidate() 614 | pingTimer = nil 615 | 616 | dispatch_async(dispatch_get_main_queue()) { 617 | self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(pingInterval, target: self, 618 | selector: Selector("sendPing"), userInfo: nil, repeats: true) 619 | } 620 | } 621 | } 622 | 623 | func stopPolling() { 624 | session.invalidateAndCancel() 625 | } 626 | 627 | private func upgradeTransport() { 628 | if websocketConnected { 629 | Logger.log("Upgrading transport to WebSockets", type: logType) 630 | 631 | fastUpgrade = true 632 | sendPollMessage("", withType: PacketType.Noop) 633 | // After this point, we should not send anymore polling messages 634 | } 635 | } 636 | 637 | /** 638 | Write a message, independent of transport. 639 | */ 640 | public func write(msg: String, withType type: PacketType, withData data: [NSData]?) { 641 | dispatch_async(emitQueue) { 642 | if self.connected { 643 | if self.websocket { 644 | Logger.log("Writing ws: %@ has data: %@", type: self.logType, args: msg, 645 | data == nil ? false : true) 646 | self.sendWebSocketMessage(msg, withType: type, datas: data) 647 | } else { 648 | Logger.log("Writing poll: %@ has data: %@", type: self.logType, args: msg, 649 | data == nil ? false : true) 650 | self.sendPollMessage(msg, withType: type, datas: data) 651 | } 652 | } 653 | } 654 | } 655 | 656 | // Delagate methods 657 | 658 | public func websocketDidConnect(socket:WebSocket) { 659 | websocketConnected = true 660 | 661 | if !forceWebsockets { 662 | probing = true 663 | probeWebSocket() 664 | } else { 665 | connected = true 666 | probing = false 667 | polling = false 668 | } 669 | } 670 | 671 | public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { 672 | websocketConnected = false 673 | probing = false 674 | 675 | if closed { 676 | client?.engineDidClose("Disconnect") 677 | return 678 | } 679 | 680 | if websocket { 681 | pingTimer?.invalidate() 682 | connected = false 683 | websocket = false 684 | 685 | let reason = error?.localizedDescription ?? "Socket Disconnected" 686 | 687 | if error != nil { 688 | client?.didError(reason) 689 | } 690 | 691 | client?.engineDidClose(reason) 692 | } else { 693 | flushProbeWait() 694 | } 695 | } 696 | 697 | public func websocketDidReceiveMessage(socket: WebSocket, text: String) { 698 | parseEngineMessage(text, fromPolling: false) 699 | } 700 | 701 | public func websocketDidReceiveData(socket: WebSocket, data: NSData) { 702 | parseEngineData(data) 703 | } 704 | } 705 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketEngineClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineClient.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/19/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | @objc public protocol SocketEngineClient { 29 | var socketURL: String {get} 30 | var secure: Bool {get} 31 | 32 | func didError(reason: AnyObject) 33 | func engineDidClose(reason: String) 34 | func parseSocketMessage(msg: String) 35 | func parseBinaryData(data: NSData) 36 | } 37 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventHandler.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | private func emitAckCallback(socket: SocketIOClient, num: Int?) -> SocketAckEmitter? { 28 | return num != nil ? SocketAckEmitter(socket: socket, ackNum: num!) : nil 29 | } 30 | 31 | struct SocketEventHandler { 32 | let event: String 33 | let callback: NormalCallback 34 | let id: NSUUID 35 | 36 | init(event: String, id: NSUUID = NSUUID(), callback: NormalCallback) { 37 | self.event = event 38 | self.id = id 39 | self.callback = callback 40 | } 41 | 42 | func executeCallback(items: [AnyObject], withAck ack: Int? = nil, withAckType type: Int? = nil, 43 | withSocket socket: SocketIOClient) { 44 | self.callback(items, emitAckCallback(socket, num: ack)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketFixUTF8.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketFixUTF8.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/16/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | func fixDoubleUTF8(inout name: String) { 29 | let utf8 = name.dataUsingEncoding(NSISOLatin1StringEncoding)! 30 | let latin1 = NSString(data: utf8, encoding: NSUTF8StringEncoding)! 31 | name = latin1 as String 32 | } 33 | 34 | func doubleEncodeUTF8(inout str: String) { 35 | let latin1 = str.dataUsingEncoding(NSUTF8StringEncoding)! 36 | let utf8 = NSString(data: latin1, encoding: NSISOLatin1StringEncoding)! 37 | str = utf8 as String 38 | } 39 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketIOClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClient.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 11/23/14. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public final class SocketIOClient: NSObject, SocketEngineClient { 28 | private let emitQueue = dispatch_queue_create("emitQueue", DISPATCH_QUEUE_SERIAL) 29 | private let handleQueue: dispatch_queue_t! 30 | 31 | public let socketURL: String 32 | 33 | public private(set) var engine: SocketEngine? 34 | public private(set) var secure = false 35 | public private(set) var status = SocketIOClientStatus.NotConnected 36 | 37 | public var nsp = "/" 38 | public var opts: [String: AnyObject]? 39 | public var reconnects = true 40 | public var reconnectWait = 10 41 | public var sid: String? { 42 | return engine?.sid 43 | } 44 | 45 | private let logType = "SocketIOClient" 46 | 47 | private var anyHandler: ((SocketAnyEvent) -> Void)? 48 | private var currentReconnectAttempt = 0 49 | private var handlers = ContiguousArray() 50 | private var connectParams: [String: AnyObject]? 51 | private var reconnectTimer: NSTimer? 52 | 53 | private let reconnectAttempts: Int! 54 | private var ackHandlers = SocketAckManager() 55 | private var currentAck = -1 56 | 57 | var waitingData = [SocketPacket]() 58 | 59 | /** 60 | Create a new SocketIOClient. opts can be omitted 61 | */ 62 | public init(var socketURL: String, opts: [String: AnyObject]? = nil) { 63 | if socketURL["https://"].matches().count != 0 { 64 | self.secure = true 65 | } 66 | 67 | socketURL = socketURL["http://"] ~= "" 68 | socketURL = socketURL["https://"] ~= "" 69 | 70 | self.socketURL = socketURL 71 | self.opts = opts 72 | 73 | if let connectParams = opts?["connectParams"] as? [String: AnyObject] { 74 | self.connectParams = connectParams 75 | } 76 | 77 | if let logger = opts?["logger"] as? SocketLogger { 78 | Logger = logger 79 | } 80 | 81 | if let log = opts?["log"] as? Bool { 82 | Logger.log = log 83 | } 84 | 85 | if let nsp = opts?["nsp"] as? String { 86 | self.nsp = nsp 87 | } 88 | 89 | if let reconnects = opts?["reconnects"] as? Bool { 90 | self.reconnects = reconnects 91 | } 92 | 93 | if let reconnectAttempts = opts?["reconnectAttempts"] as? Int { 94 | self.reconnectAttempts = reconnectAttempts 95 | } else { 96 | self.reconnectAttempts = -1 97 | } 98 | 99 | if let reconnectWait = opts?["reconnectWait"] as? Int { 100 | self.reconnectWait = abs(reconnectWait) 101 | } 102 | 103 | if let handleQueue = opts?["handleQueue"] as? dispatch_queue_t { 104 | self.handleQueue = handleQueue 105 | } else { 106 | self.handleQueue = dispatch_get_main_queue() 107 | } 108 | 109 | super.init() 110 | } 111 | 112 | deinit { 113 | Logger.log("Client is being deinit", type: logType) 114 | engine?.close(fast: true) 115 | } 116 | 117 | private func addEngine() -> SocketEngine { 118 | Logger.log("Adding engine", type: logType) 119 | 120 | let newEngine = SocketEngine(client: self, opts: opts) 121 | 122 | engine = newEngine 123 | return newEngine 124 | } 125 | 126 | private func clearReconnectTimer() { 127 | reconnectTimer?.invalidate() 128 | reconnectTimer = nil 129 | } 130 | 131 | /** 132 | Closes the socket. Only reopen the same socket if you know what you're doing. 133 | Will turn off automatic reconnects. 134 | Pass true to fast if you're closing from a background task 135 | */ 136 | public func close() { 137 | Logger.log("Closing socket", type: logType) 138 | 139 | reconnects = false 140 | didDisconnect("Closed") 141 | } 142 | 143 | /** 144 | Connect to the server. 145 | */ 146 | public func connect() { 147 | connect(timeoutAfter: 0, withTimeoutHandler: nil) 148 | } 149 | 150 | /** 151 | Connect to the server. If we aren't connected after timeoutAfter, call handler 152 | */ 153 | public func connect(timeoutAfter timeoutAfter: Int, 154 | withTimeoutHandler handler: (() -> Void)?) { 155 | assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") 156 | 157 | guard status != .Connected else { 158 | return 159 | } 160 | 161 | if status == .Closed { 162 | Logger.log("Warning! This socket was previously closed. This might be dangerous!", 163 | type: logType) 164 | } 165 | 166 | status = SocketIOClientStatus.Connecting 167 | addEngine().open(connectParams) 168 | 169 | guard timeoutAfter != 0 else { 170 | return 171 | } 172 | 173 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) 174 | 175 | dispatch_after(time, dispatch_get_main_queue()) { 176 | if self.status != .Connected { 177 | self.status = .Closed 178 | self.engine?.close(fast: true) 179 | 180 | handler?() 181 | } 182 | } 183 | } 184 | 185 | private func createOnAck(items: [AnyObject]) -> OnAckCallback { 186 | return {[weak self, ack = ++currentAck] timeout, callback in 187 | if let this = self { 188 | this.ackHandlers.addAck(ack, callback: callback) 189 | 190 | dispatch_async(this.emitQueue) { 191 | this._emit(items, ack: ack) 192 | } 193 | 194 | if timeout != 0 { 195 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeout * NSEC_PER_SEC)) 196 | 197 | dispatch_after(time, dispatch_get_main_queue()) { 198 | this.ackHandlers.timeoutAck(ack) 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | func didConnect() { 206 | Logger.log("Socket connected", type: logType) 207 | status = .Connected 208 | currentReconnectAttempt = 0 209 | clearReconnectTimer() 210 | 211 | // Don't handle as internal because something crazy could happen where 212 | // we disconnect before it's handled 213 | handleEvent("connect", data: [], isInternalMessage: false) 214 | } 215 | 216 | func didDisconnect(reason: String) { 217 | guard status != .Closed else { 218 | return 219 | } 220 | 221 | Logger.log("Disconnected: %@", type: logType, args: reason) 222 | 223 | status = .Closed 224 | reconnects = false 225 | 226 | // Make sure the engine is actually dead. 227 | engine?.close(fast: true) 228 | handleEvent("disconnect", data: [reason], isInternalMessage: true) 229 | } 230 | 231 | /// error 232 | public func didError(reason: AnyObject) { 233 | Logger.error("%@", type: logType, args: reason) 234 | 235 | handleEvent("error", data: reason as? [AnyObject] ?? [reason], 236 | isInternalMessage: true) 237 | } 238 | 239 | /** 240 | Same as close 241 | */ 242 | public func disconnect() { 243 | close() 244 | } 245 | 246 | /** 247 | Send a message to the server 248 | */ 249 | public func emit(event: String, _ items: AnyObject...) { 250 | emit(event, withItems: items) 251 | } 252 | 253 | /** 254 | Same as emit, but meant for Objective-C 255 | */ 256 | public func emit(event: String, withItems items: [AnyObject]) { 257 | guard status == .Connected else { 258 | return 259 | } 260 | 261 | dispatch_async(emitQueue) { 262 | self._emit([event] + items) 263 | } 264 | } 265 | 266 | /** 267 | Sends a message to the server, requesting an ack. Use the onAck method of SocketAckHandler to add 268 | an ack. 269 | */ 270 | public func emitWithAck(event: String, _ items: AnyObject...) -> OnAckCallback { 271 | return emitWithAck(event, withItems: items) 272 | } 273 | 274 | /** 275 | Same as emitWithAck, but for Objective-C 276 | */ 277 | public func emitWithAck(event: String, withItems items: [AnyObject]) -> OnAckCallback { 278 | return createOnAck([event] + items) 279 | } 280 | 281 | private func _emit(data: [AnyObject], ack: Int? = nil) { 282 | guard status == .Connected else { 283 | return 284 | } 285 | 286 | let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: false) 287 | let str = packet.packetString 288 | 289 | Logger.log("Emitting: %@", type: logType, args: str) 290 | 291 | if packet.type == .BinaryEvent { 292 | engine?.send(str, withData: packet.binary) 293 | } else { 294 | engine?.send(str, withData: nil) 295 | } 296 | } 297 | 298 | // If the server wants to know that the client received data 299 | func emitAck(ack: Int, withItems items: [AnyObject]) { 300 | dispatch_async(emitQueue) { 301 | if self.status == .Connected { 302 | let packet = SocketPacket.packetFromEmit(items, id: ack ?? -1, nsp: self.nsp, ack: true) 303 | let str = packet.packetString 304 | 305 | Logger.log("Emitting Ack: %@", type: self.logType, args: str) 306 | 307 | if packet.type == SocketPacket.PacketType.BinaryAck { 308 | self.engine?.send(str, withData: packet.binary) 309 | } else { 310 | self.engine?.send(str, withData: nil) 311 | } 312 | 313 | } 314 | } 315 | } 316 | 317 | public func engineDidClose(reason: String) { 318 | waitingData.removeAll() 319 | 320 | if status == .Closed || !reconnects { 321 | didDisconnect(reason) 322 | } else if status != .Reconnecting { 323 | status = .Reconnecting 324 | handleEvent("reconnect", data: [reason], isInternalMessage: true) 325 | tryReconnect() 326 | } 327 | } 328 | 329 | // Called when the socket gets an ack for something it sent 330 | func handleAck(ack: Int, data: AnyObject?) { 331 | Logger.log("Handling ack: %@ with data: %@", type: logType, args: ack, data ?? "") 332 | 333 | ackHandlers.executeAck(ack, 334 | items: (data as? [AnyObject]) ?? (data != nil ? [data!] : [])) 335 | } 336 | 337 | /** 338 | Causes an event to be handled. Only use if you know what you're doing. 339 | */ 340 | public func handleEvent(event: String, data: [AnyObject], isInternalMessage: Bool, 341 | wantsAck ack: Int? = nil) { 342 | guard status == .Connected || isInternalMessage else { 343 | return 344 | } 345 | 346 | Logger.log("Handling event: %@ with data: %@", type: logType, args: event, data ?? "") 347 | 348 | if anyHandler != nil { 349 | dispatch_async(handleQueue) { 350 | self.anyHandler?(SocketAnyEvent(event: event, items: data)) 351 | } 352 | } 353 | 354 | for handler in handlers where handler.event == event { 355 | if let ack = ack { 356 | dispatch_async(handleQueue) { 357 | handler.executeCallback(data, withAck: ack, withSocket: self) 358 | } 359 | } else { 360 | dispatch_async(handleQueue) { 361 | handler.executeCallback(data, withAck: ack, withSocket: self) 362 | } 363 | } 364 | } 365 | } 366 | 367 | /** 368 | Leaves nsp and goes back to / 369 | */ 370 | public func leaveNamespace() { 371 | if nsp != "/" { 372 | engine?.send("1\(nsp)", withData: nil) 373 | nsp = "/" 374 | } 375 | } 376 | 377 | /** 378 | Joins nsp if it is not / 379 | */ 380 | public func joinNamespace() { 381 | Logger.log("Joining namespace", type: logType) 382 | 383 | if nsp != "/" { 384 | engine?.send("0\(nsp)", withData: nil) 385 | } 386 | } 387 | 388 | /** 389 | Joins namespace / 390 | */ 391 | public func joinNamespace(namespace: String) { 392 | self.nsp = namespace 393 | joinNamespace() 394 | } 395 | 396 | /** 397 | Removes handler(s) 398 | */ 399 | public func off(event: String) { 400 | Logger.log("Removing handler for event: %@", type: logType, args: event) 401 | 402 | handlers = ContiguousArray(handlers.filter { $0.event != event }) 403 | } 404 | 405 | /** 406 | Adds a handler for an event. 407 | */ 408 | public func on(event: String, callback: NormalCallback) { 409 | Logger.log("Adding handler for event: %@", type: logType, args: event) 410 | 411 | let handler = SocketEventHandler(event: event, callback: callback) 412 | handlers.append(handler) 413 | } 414 | 415 | /** 416 | Adds a single-use handler for an event. 417 | */ 418 | public func once(event: String, callback: NormalCallback) { 419 | Logger.log("Adding once handler for event: %@", type: logType, args: event) 420 | 421 | let id = NSUUID() 422 | 423 | let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in 424 | guard let this = self else {return} 425 | this.handlers = ContiguousArray(this.handlers.filter {$0.id != id}) 426 | callback(data, ack) 427 | } 428 | 429 | handlers.append(handler) 430 | } 431 | 432 | /** 433 | Removes all handlers. 434 | Can be used after disconnecting to break any potential remaining retain cycles. 435 | */ 436 | public func removeAllHandlers() { 437 | handlers.removeAll(keepCapacity: false) 438 | } 439 | 440 | /** 441 | Adds a handler that will be called on every event. 442 | */ 443 | public func onAny(handler: (SocketAnyEvent) -> Void) { 444 | anyHandler = handler 445 | } 446 | 447 | /** 448 | Same as connect 449 | */ 450 | public func open() { 451 | connect() 452 | } 453 | 454 | public func parseSocketMessage(msg: String) { 455 | dispatch_async(handleQueue) { 456 | SocketParser.parseSocketMessage(msg, socket: self) 457 | } 458 | } 459 | 460 | public func parseBinaryData(data: NSData) { 461 | dispatch_async(handleQueue) { 462 | SocketParser.parseBinaryData(data, socket: self) 463 | } 464 | } 465 | 466 | /** 467 | Tries to reconnect to the server. 468 | */ 469 | public func reconnect() { 470 | engine?.stopPolling() 471 | tryReconnect() 472 | } 473 | 474 | private func tryReconnect() { 475 | if reconnectTimer == nil { 476 | Logger.log("Starting reconnect", type: logType) 477 | 478 | status = .Reconnecting 479 | 480 | dispatch_async(dispatch_get_main_queue()) { 481 | self.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self.reconnectWait), 482 | target: self, selector: "_tryReconnect", userInfo: nil, repeats: true) 483 | } 484 | } 485 | } 486 | 487 | @objc private func _tryReconnect() { 488 | if status == .Connected { 489 | clearReconnectTimer() 490 | 491 | return 492 | } 493 | 494 | 495 | if reconnectAttempts != -1 && currentReconnectAttempt + 1 > reconnectAttempts || !reconnects { 496 | clearReconnectTimer() 497 | didDisconnect("Reconnect Failed") 498 | 499 | return 500 | } 501 | 502 | Logger.log("Trying to reconnect", type: logType) 503 | handleEvent("reconnectAttempt", data: [reconnectAttempts - currentReconnectAttempt], 504 | isInternalMessage: true) 505 | 506 | currentReconnectAttempt++ 507 | connect() 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketIOClientStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClientStatus.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 8/14/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | @objc public enum SocketIOClientStatus: Int, CustomStringConvertible { 28 | public var description: String { 29 | switch self { 30 | case NotConnected: 31 | return "Not Connected" 32 | case Closed: 33 | return "Closed" 34 | case Connecting: 35 | return "Connecting" 36 | case Connected: 37 | return "Connected" 38 | case Reconnecting: 39 | return "Reconnecting" 40 | } 41 | } 42 | 43 | case NotConnected, Closed, Connecting, Connected, Reconnecting 44 | } -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketLogger.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/11/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | var Logger: SocketLogger = DefaultSocketLogger() 28 | 29 | public protocol SocketLogger { 30 | /// Whether to log or not 31 | var log: Bool {get set} 32 | 33 | /// Normal log messages 34 | func log(message: String, type: String, args: AnyObject...) 35 | 36 | /// Error Messages 37 | func error(message: String, type: String, args: AnyObject...) 38 | } 39 | 40 | public extension SocketLogger { 41 | func log(message: String, type: String, args: AnyObject...) { 42 | abstractLog("Log", message: message, type: type, args: args) 43 | } 44 | 45 | func error(message: String, type: String, args: AnyObject...) { 46 | abstractLog("ERROR", message: message, type: type, args: args) 47 | } 48 | 49 | private func abstractLog(logType: String, message: String, type: String, args: [AnyObject]) { 50 | guard log else { return } 51 | 52 | let newArgs = args.map {arg -> CVarArgType in String(arg)} 53 | let replaced = String(format: message, arguments: newArgs) 54 | 55 | NSLog("%@ %@: %@", logType, type, replaced) 56 | } 57 | } 58 | 59 | struct DefaultSocketLogger: SocketLogger { 60 | var log = false 61 | } 62 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketPacket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketPacket.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | struct SocketPacket { 28 | private let placeholders: Int 29 | private var currentPlace = 0 30 | 31 | private static let logType = "SocketPacket" 32 | 33 | let nsp: String 34 | let id: Int 35 | let type: PacketType 36 | 37 | enum PacketType: Int { 38 | case Connect, Disconnect, Event, Ack, Error, BinaryEvent, BinaryAck 39 | 40 | init?(str: String) { 41 | if let int = Int(str), raw = PacketType(rawValue: int) { 42 | self = raw 43 | } else { 44 | return nil 45 | } 46 | } 47 | } 48 | 49 | var args: [AnyObject]? { 50 | var arr = data 51 | 52 | if data.count == 0 { 53 | return nil 54 | } else { 55 | if type == PacketType.Event || type == PacketType.BinaryEvent { 56 | arr.removeAtIndex(0) 57 | return arr 58 | } else { 59 | return arr 60 | } 61 | } 62 | } 63 | 64 | var binary: [NSData] 65 | var data: [AnyObject] 66 | var description: String { 67 | return "SocketPacket {type: \(String(type.rawValue)); data: " + 68 | "\(String(data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" 69 | } 70 | 71 | var event: String { 72 | return data[0] as? String ?? String(data[0]) 73 | } 74 | 75 | var packetString: String { 76 | return createPacketString() 77 | } 78 | 79 | init(type: SocketPacket.PacketType, data: [AnyObject] = [AnyObject](), id: Int = -1, 80 | nsp: String, placeholders: Int = 0, binary: [NSData] = [NSData]()) { 81 | self.data = data 82 | self.id = id 83 | self.nsp = nsp 84 | self.type = type 85 | self.placeholders = placeholders 86 | self.binary = binary 87 | } 88 | 89 | mutating func addData(data: NSData) -> Bool { 90 | if placeholders == currentPlace { 91 | return true 92 | } 93 | 94 | binary.append(data) 95 | currentPlace++ 96 | 97 | if placeholders == currentPlace { 98 | currentPlace = 0 99 | return true 100 | } else { 101 | return false 102 | } 103 | } 104 | 105 | private func completeMessage(var message: String, ack: Bool) -> String { 106 | if data.count == 0 { 107 | return message + "]" 108 | } 109 | 110 | for arg in data { 111 | if arg is NSDictionary || arg is [AnyObject] { 112 | do { 113 | let jsonSend = try NSJSONSerialization.dataWithJSONObject(arg, 114 | options: NSJSONWritingOptions(rawValue: 0)) 115 | let jsonString = NSString(data: jsonSend, encoding: NSUTF8StringEncoding) 116 | 117 | message += jsonString! as String + "," 118 | } catch { 119 | Logger.error("Error creating JSON object in SocketPacket.completeMessage", type: SocketPacket.logType) 120 | } 121 | } else if var str = arg as? String { 122 | str = str["\n"] ~= "\\\\n" 123 | str = str["\r"] ~= "\\\\r" 124 | 125 | message += "\"\(str)\"," 126 | } else if arg is NSNull { 127 | message += "null," 128 | } else { 129 | message += "\(arg)," 130 | } 131 | } 132 | 133 | if message != "" { 134 | message.removeAtIndex(message.endIndex.predecessor()) 135 | } 136 | 137 | return message + "]" 138 | } 139 | 140 | private func createAck() -> String { 141 | let msg: String 142 | 143 | if type == PacketType.Ack { 144 | if nsp == "/" { 145 | msg = "3\(id)[" 146 | } else { 147 | msg = "3\(nsp),\(id)[" 148 | } 149 | } else { 150 | if nsp == "/" { 151 | msg = "6\(binary.count)-\(id)[" 152 | } else { 153 | msg = "6\(binary.count)-/\(nsp),\(id)[" 154 | } 155 | } 156 | 157 | return completeMessage(msg, ack: true) 158 | } 159 | 160 | 161 | private func createMessageForEvent() -> String { 162 | let message: String 163 | 164 | if type == PacketType.Event { 165 | if nsp == "/" { 166 | if id == -1 { 167 | message = "2[" 168 | } else { 169 | message = "2\(id)[" 170 | } 171 | } else { 172 | if id == -1 { 173 | message = "2\(nsp),[" 174 | } else { 175 | message = "2\(nsp),\(id)[" 176 | } 177 | } 178 | } else { 179 | if nsp == "/" { 180 | if id == -1 { 181 | message = "5\(binary.count)-[" 182 | } else { 183 | message = "5\(binary.count)-\(id)[" 184 | } 185 | } else { 186 | if id == -1 { 187 | message = "5\(binary.count)-\(nsp),[" 188 | } else { 189 | message = "5\(binary.count)-\(nsp),\(id)[" 190 | } 191 | } 192 | } 193 | 194 | return completeMessage(message, ack: false) 195 | } 196 | 197 | private func createPacketString() -> String { 198 | let str: String 199 | 200 | if type == PacketType.Event || type == PacketType.BinaryEvent { 201 | str = createMessageForEvent() 202 | } else { 203 | str = createAck() 204 | } 205 | 206 | return str 207 | } 208 | 209 | mutating func fillInPlaceholders() { 210 | for i in 0.. AnyObject { 220 | if let str = data as? String { 221 | if let num = str["~~(\\d)"].groups() { 222 | return binary[Int(num[1])!] 223 | } else { 224 | return str 225 | } 226 | } else if let dict = data as? NSDictionary { 227 | let newDict = NSMutableDictionary(dictionary: dict) 228 | 229 | for (key, value) in dict { 230 | newDict[key as! NSCopying] = _fillInPlaceholders(value) 231 | } 232 | 233 | return newDict 234 | } else if let arr = data as? NSArray { 235 | let newArr = NSMutableArray(array: arr) 236 | 237 | for i in 0.. PacketType { 250 | switch binCount { 251 | case 0 where !ack: 252 | return PacketType.Event 253 | case 0 where ack: 254 | return PacketType.Ack 255 | case _ where !ack: 256 | return PacketType.BinaryEvent 257 | case _ where ack: 258 | return PacketType.BinaryAck 259 | default: 260 | return PacketType.Error 261 | } 262 | } 263 | 264 | static func packetFromEmit(items: [AnyObject], id: Int, nsp: String, ack: Bool) -> SocketPacket { 265 | let (parsedData, binary) = deconstructData(items) 266 | let packet = SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, 267 | id: id, nsp: nsp, placeholders: -1, binary: binary) 268 | 269 | return packet 270 | } 271 | } 272 | 273 | private extension SocketPacket { 274 | static func shred(data: AnyObject, inout binary: [NSData]) -> AnyObject { 275 | if let bin = data as? NSData { 276 | let placeholder = ["_placeholder" :true, "num": binary.count] 277 | 278 | binary.append(bin) 279 | 280 | return placeholder 281 | } else if var arr = data as? [AnyObject] { 282 | for i in 0.. ([AnyObject], [NSData]) { 301 | var binary = [NSData]() 302 | 303 | for i in 0.. Bool { 28 | return nsp == socket.nsp 29 | } 30 | 31 | private static func handleEvent(p: SocketPacket, socket: SocketIOClient) { 32 | guard isCorrectNamespace(p.nsp, socket) else { return } 33 | 34 | socket.handleEvent(p.event, data: p.args ?? [], 35 | isInternalMessage: false, wantsAck: p.id) 36 | } 37 | 38 | private static func handleAck(p: SocketPacket, socket: SocketIOClient) { 39 | guard isCorrectNamespace(p.nsp, socket) else { return } 40 | 41 | socket.handleAck(p.id, data: p.data) 42 | } 43 | 44 | private static func handleBinary(p: SocketPacket, socket: SocketIOClient) { 45 | guard isCorrectNamespace(p.nsp, socket) else { return } 46 | 47 | socket.waitingData.append(p) 48 | } 49 | 50 | private static func handleConnect(p: SocketPacket, socket: SocketIOClient) { 51 | if p.nsp == "/" && socket.nsp != "/" { 52 | socket.joinNamespace() 53 | } else if p.nsp != "/" && socket.nsp == "/" { 54 | socket.didConnect() 55 | } else { 56 | socket.didConnect() 57 | } 58 | } 59 | 60 | static func parseString(message: String) -> SocketPacket? { 61 | var parser = SocketStringReader(message: message) 62 | 63 | guard let type = SocketPacket.PacketType(str: parser.read(1)) 64 | else {return nil} 65 | 66 | if !parser.hasNext { 67 | return SocketPacket(type: type, nsp: "/") 68 | } 69 | 70 | var namespace: String? 71 | var placeholders = -1 72 | 73 | if type == .BinaryEvent || type == .BinaryAck { 74 | if let holders = Int(parser.readUntilStringOccurence("-")) { 75 | placeholders = holders 76 | } else { 77 | return nil 78 | } 79 | } 80 | 81 | if parser.currentCharacter == "/" { 82 | namespace = parser.readUntilStringOccurence(",") ?? parser.readUntilEnd() 83 | } 84 | 85 | if !parser.hasNext { 86 | return SocketPacket(type: type, id: -1, 87 | nsp: namespace ?? "/", placeholders: placeholders) 88 | } 89 | 90 | var idString = "" 91 | 92 | while parser.hasNext { 93 | if let int = Int(parser.read(1)) { 94 | idString += String(int) 95 | } else { 96 | parser.advanceIndexBy(-2) 97 | break 98 | } 99 | } 100 | 101 | let d = message[parser.currentIndex.advancedBy(1).. AnyObject? { 111 | let stringData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) 112 | do { 113 | return try NSJSONSerialization.JSONObjectWithData(stringData!, 114 | options: NSJSONReadingOptions.MutableContainers) 115 | } catch { 116 | Logger.error("Parsing JSON: %@", type: "SocketParser", args: data) 117 | return nil 118 | } 119 | } 120 | 121 | // Parses messages recieved 122 | static func parseSocketMessage(message: String, socket: SocketIOClient) { 123 | guard !message.isEmpty else { return } 124 | 125 | Logger.log("Parsing %@", type: "SocketParser", args: message) 126 | 127 | guard let pack = parseString(message) else { 128 | Logger.error("Parsing message: %@", type: "SocketParser", args: message) 129 | return 130 | } 131 | 132 | Logger.log("Decoded packet as: %@", type: "SocketParser", args: pack.description) 133 | 134 | switch pack.type { 135 | case .Event: 136 | handleEvent(pack, socket: socket) 137 | case .Ack: 138 | handleAck(pack, socket: socket) 139 | case .BinaryEvent: 140 | handleBinary(pack, socket: socket) 141 | case .BinaryAck: 142 | handleBinary(pack, socket: socket) 143 | case .Connect: 144 | handleConnect(pack, socket: socket) 145 | case .Disconnect: 146 | socket.didDisconnect("Got Disconnect") 147 | case .Error: 148 | socket.didError("Error: \(pack.data)") 149 | } 150 | 151 | } 152 | 153 | static func parseBinaryData(data: NSData, socket: SocketIOClient) { 154 | guard !socket.waitingData.isEmpty else { 155 | Logger.error("Got data when not remaking packet", type: "SocketParser") 156 | return 157 | } 158 | 159 | let shouldExecute = socket.waitingData[socket.waitingData.count - 1].addData(data) 160 | 161 | guard shouldExecute else { 162 | return 163 | } 164 | 165 | var packet = socket.waitingData.removeLast() 166 | packet.fillInPlaceholders() 167 | 168 | if packet.type != .BinaryAck { 169 | socket.handleEvent(packet.event, data: packet.args ?? [], 170 | isInternalMessage: false, wantsAck: packet.id) 171 | } else { 172 | socket.handleAck(packet.id, data: packet.args) 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketStringReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketStringReader.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Lukas Schmidt on 07.09.15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | struct SocketStringReader { 26 | let message: String 27 | var currentIndex: String.Index 28 | var hasNext: Bool { 29 | return currentIndex != message.endIndex 30 | } 31 | 32 | var currentCharacter: String { 33 | return String(message[currentIndex]) 34 | } 35 | 36 | init(message: String) { 37 | self.message = message 38 | currentIndex = message.startIndex 39 | } 40 | 41 | mutating func advanceIndexBy(n: Int) { 42 | currentIndex = currentIndex.advancedBy(n) 43 | } 44 | 45 | mutating func read(readLength: Int) -> String { 46 | let readString = message[currentIndex.. String { 53 | let substring = message[currentIndex.. String { 66 | return read(currentIndex.distanceTo(message.endIndex)) 67 | } 68 | } -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SocketTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketTypes.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/8/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | public typealias AckCallback = ([AnyObject]) -> Void 28 | public typealias NormalCallback = ([AnyObject], SocketAckEmitter?) -> Void 29 | public typealias OnAckCallback = (timeoutAfter: UInt64, callback: AckCallback) -> Void 30 | 31 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/SwiftRegex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftRegex.swift 3 | // SwiftRegex 4 | // 5 | // Created by John Holdsworth on 26/06/2014. 6 | // Copyright (c) 2014 John Holdsworth. 7 | // 8 | // $Id: //depot/SwiftRegex/SwiftRegex.swift#37 $ 9 | // 10 | // This code is in the public domain from: 11 | // https://github.com/johnno1962/SwiftRegex 12 | // 13 | 14 | import Foundation 15 | 16 | private var swiftRegexCache = [String: NSRegularExpression]() 17 | 18 | internal class SwiftRegex: NSObject, BooleanType { 19 | var target:String 20 | var regex: NSRegularExpression 21 | 22 | init(target:String, pattern:String, options:NSRegularExpressionOptions?) { 23 | self.target = target 24 | if let regex = swiftRegexCache[pattern] { 25 | self.regex = regex 26 | } else { 27 | do { 28 | let regex = try NSRegularExpression(pattern: pattern, options: 29 | NSRegularExpressionOptions.DotMatchesLineSeparators) 30 | swiftRegexCache[pattern] = regex 31 | self.regex = regex 32 | } catch let error as NSError { 33 | SwiftRegex.failure("Error in pattern: \(pattern) - \(error)") 34 | self.regex = NSRegularExpression() 35 | } 36 | } 37 | super.init() 38 | } 39 | 40 | private static func failure(message: String) { 41 | fatalError("SwiftRegex: \(message)") 42 | } 43 | 44 | private final var targetRange: NSRange { 45 | return NSRange(location: 0,length: target.utf16.count) 46 | } 47 | 48 | private final func substring(range: NSRange) -> String? { 49 | if range.location != NSNotFound { 50 | return (target as NSString).substringWithRange(range) 51 | } else { 52 | return nil 53 | } 54 | } 55 | 56 | func doesMatch(options: NSMatchingOptions!) -> Bool { 57 | return range(options).location != NSNotFound 58 | } 59 | 60 | func range(options: NSMatchingOptions) -> NSRange { 61 | return regex.rangeOfFirstMatchInString(target as String, options: [], range: targetRange) 62 | } 63 | 64 | func match(options: NSMatchingOptions) -> String? { 65 | return substring(range(options)) 66 | } 67 | 68 | func groups() -> [String]? { 69 | return groupsForMatch(regex.firstMatchInString(target as String, options: 70 | NSMatchingOptions.WithoutAnchoringBounds, range: targetRange)) 71 | } 72 | 73 | private func groupsForMatch(match: NSTextCheckingResult?) -> [String]? { 74 | guard let match = match else { 75 | return nil 76 | } 77 | var groups = [String]() 78 | for groupno in 0...regex.numberOfCaptureGroups { 79 | if let group = substring(match.rangeAtIndex(groupno)) { 80 | groups += [group] 81 | } else { 82 | groups += ["_"] // avoids bridging problems 83 | } 84 | } 85 | return groups 86 | } 87 | 88 | subscript(groupno: Int) -> String? { 89 | get { 90 | return groups()?[groupno] 91 | } 92 | 93 | set(newValue) { 94 | if newValue == nil { 95 | return 96 | } 97 | 98 | for match in Array(matchResults().reverse()) { 99 | let replacement = regex.replacementStringForResult(match, 100 | inString: target as String, offset: 0, template: newValue!) 101 | let mut = NSMutableString(string: target) 102 | mut.replaceCharactersInRange(match.rangeAtIndex(groupno), withString: replacement) 103 | 104 | target = mut as String 105 | } 106 | } 107 | } 108 | 109 | func matchResults() -> [NSTextCheckingResult] { 110 | let matches = regex.matchesInString(target as String, options: 111 | NSMatchingOptions.WithoutAnchoringBounds, range: targetRange) 112 | as [NSTextCheckingResult] 113 | 114 | return matches 115 | } 116 | 117 | func ranges() -> [NSRange] { 118 | return matchResults().map { $0.range } 119 | } 120 | 121 | func matches() -> [String] { 122 | return matchResults().map( { self.substring($0.range)!}) 123 | } 124 | 125 | func allGroups() -> [[String]?] { 126 | return matchResults().map { self.groupsForMatch($0) } 127 | } 128 | 129 | func dictionary(options: NSMatchingOptions!) -> Dictionary { 130 | var out = Dictionary() 131 | for match in matchResults() { 132 | out[substring(match.rangeAtIndex(1))!] = substring(match.rangeAtIndex(2))! 133 | } 134 | return out 135 | } 136 | 137 | func substituteMatches(substitution: ((NSTextCheckingResult, UnsafeMutablePointer) -> String), 138 | options:NSMatchingOptions) -> String { 139 | let out = NSMutableString() 140 | var pos = 0 141 | 142 | regex.enumerateMatchesInString(target as String, options: options, range: targetRange ) {match, flags, stop in 143 | let matchRange = match!.range 144 | out.appendString( self.substring(NSRange(location:pos, length:matchRange.location-pos))!) 145 | out.appendString( substitution(match!, stop) ) 146 | pos = matchRange.location + matchRange.length 147 | } 148 | 149 | out.appendString(substring(NSRange(location:pos, length:targetRange.length-pos))!) 150 | 151 | return out as String 152 | } 153 | 154 | var boolValue: Bool { 155 | return doesMatch(nil) 156 | } 157 | } 158 | 159 | extension String { 160 | subscript(pattern: String, options: NSRegularExpressionOptions) -> SwiftRegex { 161 | return SwiftRegex(target: self, pattern: pattern, options: options) 162 | } 163 | } 164 | 165 | extension String { 166 | subscript(pattern: String) -> SwiftRegex { 167 | return SwiftRegex(target: self, pattern: pattern, options: nil) 168 | } 169 | } 170 | 171 | func ~= (left: SwiftRegex, right: String) -> String { 172 | return left.substituteMatches({match, stop in 173 | return left.regex.replacementStringForResult( match, 174 | inString: left.target as String, offset: 0, template: right ) 175 | }, options: []) 176 | } 177 | 178 | func ~= (left: SwiftRegex, right: [String]) -> String { 179 | var matchNumber = 0 180 | return left.substituteMatches({match, stop -> String in 181 | 182 | if ++matchNumber == right.count { 183 | stop.memory = true 184 | } 185 | 186 | return left.regex.replacementStringForResult( match, 187 | inString: left.target as String, offset: 0, template: right[matchNumber-1] ) 188 | }, options: []) 189 | } 190 | 191 | func ~= (left: SwiftRegex, right: (String) -> String) -> String { 192 | // return right(left.substring(match.range)) 193 | return left.substituteMatches( 194 | {match, stop -> String in 195 | right(left.substring(match.range)!) 196 | }, options: []) 197 | } 198 | 199 | func ~= (left: SwiftRegex, right: ([String]?) -> String) -> String { 200 | return left.substituteMatches({match, stop -> String in 201 | return right(left.groupsForMatch(match)) 202 | }, options: []) 203 | } 204 | -------------------------------------------------------------------------------- /RNSwiftSocketIO/SocketIOClient/WebSocket.swift: -------------------------------------------------------------------------------- 1 | ////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Websocket.swift 4 | // 5 | // Created by Dalton Cherry on 7/16/14. 6 | // 7 | ////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | import Foundation 10 | import CoreFoundation 11 | 12 | public protocol WebSocketDelegate: class { 13 | func websocketDidConnect(socket: WebSocket) 14 | func websocketDidDisconnect(socket: WebSocket, error: NSError?) 15 | func websocketDidReceiveMessage(socket: WebSocket, text: String) 16 | func websocketDidReceiveData(socket: WebSocket, data: NSData) 17 | } 18 | 19 | public protocol WebSocketPongDelegate: class { 20 | func websocketDidReceivePong(socket: WebSocket) 21 | } 22 | 23 | public class WebSocket : NSObject, NSStreamDelegate { 24 | 25 | enum OpCode : UInt8 { 26 | case ContinueFrame = 0x0 27 | case TextFrame = 0x1 28 | case BinaryFrame = 0x2 29 | //3-7 are reserved. 30 | case ConnectionClose = 0x8 31 | case Ping = 0x9 32 | case Pong = 0xA 33 | //B-F reserved. 34 | } 35 | 36 | enum CloseCode : UInt16 { 37 | case Normal = 1000 38 | case GoingAway = 1001 39 | case ProtocolError = 1002 40 | case ProtocolUnhandledType = 1003 41 | // 1004 reserved. 42 | case NoStatusReceived = 1005 43 | //1006 reserved. 44 | case Encoding = 1007 45 | case PolicyViolated = 1008 46 | case MessageTooBig = 1009 47 | } 48 | 49 | enum InternalErrorCode : UInt16 { 50 | // 0-999 WebSocket status codes not used 51 | case OutputStreamWriteError = 1 52 | } 53 | 54 | //Where the callback is executed. It defaults to the main UI thread queue. 55 | public var queue = dispatch_get_main_queue() 56 | 57 | var optionalProtocols : Array? 58 | //Constant Values. 59 | let headerWSUpgradeName = "Upgrade" 60 | let headerWSUpgradeValue = "websocket" 61 | let headerWSHostName = "Host" 62 | let headerWSConnectionName = "Connection" 63 | let headerWSConnectionValue = "Upgrade" 64 | let headerWSProtocolName = "Sec-WebSocket-Protocol" 65 | let headerWSVersionName = "Sec-WebSocket-Version" 66 | let headerWSVersionValue = "13" 67 | let headerWSKeyName = "Sec-WebSocket-Key" 68 | let headerOriginName = "Origin" 69 | let headerWSAcceptName = "Sec-WebSocket-Accept" 70 | let BUFFER_MAX = 4096 71 | let FinMask: UInt8 = 0x80 72 | let OpCodeMask: UInt8 = 0x0F 73 | let RSVMask: UInt8 = 0x70 74 | let MaskMask: UInt8 = 0x80 75 | let PayloadLenMask: UInt8 = 0x7F 76 | let MaxFrameSize: Int = 32 77 | 78 | class WSResponse { 79 | var isFin = false 80 | var code: OpCode = .ContinueFrame 81 | var bytesLeft = 0 82 | var frameCount = 0 83 | var buffer: NSMutableData? 84 | } 85 | 86 | public weak var delegate: WebSocketDelegate? 87 | public weak var pongDelegate: WebSocketPongDelegate? 88 | public var onConnect: ((Void) -> Void)? 89 | public var onDisconnect: ((NSError?) -> Void)? 90 | public var onText: ((String) -> Void)? 91 | public var onData: ((NSData) -> Void)? 92 | public var onPong: ((Void) -> Void)? 93 | public var headers = Dictionary() 94 | public var voipEnabled = false 95 | public var selfSignedSSL = false 96 | private var security: Security? 97 | public var isConnected :Bool { 98 | return connected 99 | } 100 | 101 | private var cookies:[NSHTTPCookie]? 102 | private var url: NSURL 103 | private var inputStream: NSInputStream? 104 | private var outputStream: NSOutputStream? 105 | private var isRunLoop = false 106 | private var connected = false 107 | private var isCreated = false 108 | private var writeQueue: NSOperationQueue? 109 | private var readStack = Array() 110 | private var inputQueue = Array() 111 | private var fragBuffer: NSData? 112 | private var certValidated = false 113 | private var didDisconnect = false 114 | 115 | //init the websocket with a url 116 | public init(url: NSURL) { 117 | self.url = url 118 | } 119 | 120 | public convenience init(url: NSURL, cookies:[NSHTTPCookie]?) { 121 | self.init(url: url) 122 | self.cookies = cookies 123 | } 124 | 125 | //used for setting protocols. 126 | public convenience init(url: NSURL, protocols: Array) { 127 | self.init(url: url) 128 | optionalProtocols = protocols 129 | } 130 | 131 | ///Connect to the websocket server on a background thread 132 | public func connect() { 133 | if isCreated { 134 | return 135 | } 136 | 137 | dispatch_async(queue, { [weak self] in 138 | guard let weakSelf = self else { 139 | return 140 | } 141 | 142 | weakSelf.didDisconnect = false 143 | }) 144 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { [weak self] in 145 | guard let weakSelf = self else { 146 | return 147 | } 148 | 149 | weakSelf.isCreated = true 150 | weakSelf.createHTTPRequest() 151 | weakSelf.isCreated = false 152 | }) 153 | } 154 | 155 | ///disconnect from the websocket server 156 | public func disconnect() { 157 | writeError(CloseCode.Normal.rawValue) 158 | } 159 | 160 | ///write a string to the websocket. This sends it as a text frame. 161 | public func writeString(str: String) { 162 | dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame) 163 | } 164 | 165 | ///write binary data to the websocket. This sends it as a binary frame. 166 | public func writeData(data: NSData) { 167 | dequeueWrite(data, code: .BinaryFrame) 168 | } 169 | 170 | //write a ping to the websocket. This sends it as a control frame. 171 | //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s 172 | public func writePing(data: NSData) { 173 | dequeueWrite(data, code: .Ping) 174 | } 175 | //private methods below! 176 | 177 | //private method that starts the connection 178 | private func createHTTPRequest() { 179 | 180 | let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET", 181 | url, kCFHTTPVersion1_1).takeRetainedValue() 182 | 183 | var port = url.port 184 | if port == nil { 185 | if url.scheme == "wss" || url.scheme == "https" { 186 | port = 443 187 | } else { 188 | port = 80 189 | } 190 | } 191 | 192 | if self.cookies != nil { 193 | let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(self.cookies!) 194 | for (key, value) in headers { 195 | self.addHeader(urlRequest, key: key as String, val: value as String) 196 | } 197 | } 198 | 199 | self.addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue) 200 | self.addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue) 201 | if let protocols = optionalProtocols { 202 | self.addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joinWithSeparator(",")) 203 | } 204 | self.addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) 205 | self.addHeader(urlRequest, key: headerWSKeyName, val: self.generateWebSocketKey()) 206 | self.addHeader(urlRequest, key: headerOriginName, val: url.absoluteString) 207 | self.addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") 208 | for (key,value) in headers { 209 | self.addHeader(urlRequest, key: key, val: value) 210 | } 211 | 212 | 213 | let serializedRequest: NSData = CFHTTPMessageCopySerializedMessage(urlRequest)!.takeRetainedValue() 214 | self.initStreamsWithData(serializedRequest, Int(port!)) 215 | } 216 | //Add a header to the CFHTTPMessage by using the NSString bridges to CFString 217 | private func addHeader(urlRequest: CFHTTPMessage,key: String, val: String) { 218 | let nsKey: NSString = key 219 | let nsVal: NSString = val 220 | CFHTTPMessageSetHeaderFieldValue(urlRequest, 221 | nsKey, 222 | nsVal) 223 | } 224 | //generate a websocket key as needed in rfc 225 | private func generateWebSocketKey() -> String { 226 | var key = "" 227 | let seed = 16 228 | for (var i = 0; i < seed; i++) { 229 | let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25))) 230 | key += "\(Character(uni))" 231 | } 232 | let data = key.dataUsingEncoding(NSUTF8StringEncoding) 233 | let baseKey = data?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) 234 | return baseKey! 235 | } 236 | //Start the stream connection and write the data to the output stream 237 | private func initStreamsWithData(data: NSData, _ port: Int) { 238 | //higher level API we will cut over to at some point 239 | //NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream) 240 | 241 | var readStream: Unmanaged? 242 | var writeStream: Unmanaged? 243 | let h: NSString = url.host! 244 | CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream) 245 | inputStream = readStream!.takeRetainedValue() 246 | outputStream = writeStream!.takeRetainedValue() 247 | 248 | inputStream!.delegate = self 249 | outputStream!.delegate = self 250 | if url.scheme == "wss" || url.scheme == "https" { 251 | inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) 252 | outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL, forKey: NSStreamSocketSecurityLevelKey) 253 | } else { 254 | certValidated = true //not a https session, so no need to check SSL pinning 255 | } 256 | if self.voipEnabled { 257 | inputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) 258 | outputStream!.setProperty(NSStreamNetworkServiceTypeVoIP, forKey: NSStreamNetworkServiceType) 259 | } 260 | if self.selfSignedSSL { 261 | let settings: Dictionary = [kCFStreamSSLValidatesCertificateChain: NSNumber(bool:false), kCFStreamSSLPeerName: kCFNull] 262 | inputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) 263 | outputStream!.setProperty(settings, forKey: kCFStreamPropertySSLSettings as String) 264 | } 265 | isRunLoop = true 266 | inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 267 | outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 268 | inputStream!.open() 269 | outputStream!.open() 270 | let bytes = UnsafePointer(data.bytes) 271 | outputStream!.write(bytes, maxLength: data.length) 272 | while(isRunLoop) { 273 | NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as NSDate) 274 | } 275 | } 276 | //delegate for the stream methods. Processes incoming bytes 277 | public func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) { 278 | 279 | if let sec = security where !certValidated && (eventCode == .HasBytesAvailable || eventCode == .HasSpaceAvailable) { 280 | let possibleTrust: AnyObject? = aStream.propertyForKey(kCFStreamPropertySSLPeerTrust as String) 281 | if let trust: AnyObject = possibleTrust { 282 | let domain: AnyObject? = aStream.propertyForKey(kCFStreamSSLPeerName as String) 283 | if sec.isValid(trust as! SecTrustRef, domain: domain as! String?) { 284 | certValidated = true 285 | } else { 286 | let error = self.errorWithDetail("Invalid SSL certificate", code: 1) 287 | doDisconnect(error) 288 | disconnectStream(error) 289 | return 290 | } 291 | } 292 | } 293 | if eventCode == .HasBytesAvailable { 294 | if(aStream == inputStream) { 295 | processInputStream() 296 | } 297 | } else if eventCode == .ErrorOccurred { 298 | disconnectStream(aStream.streamError) 299 | } else if eventCode == .EndEncountered { 300 | disconnectStream(nil) 301 | } 302 | } 303 | //disconnect the stream object 304 | private func disconnectStream(error: NSError?) { 305 | if writeQueue != nil { 306 | writeQueue!.waitUntilAllOperationsAreFinished() 307 | } 308 | if let stream = inputStream { 309 | stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 310 | stream.close() 311 | } 312 | if let stream = outputStream { 313 | stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) 314 | stream.close() 315 | } 316 | outputStream = nil 317 | isRunLoop = false 318 | certValidated = false 319 | self.doDisconnect(error) 320 | connected = false 321 | } 322 | 323 | ///handles the incoming bytes and sending them to the proper processing method 324 | private func processInputStream() { 325 | let buf = NSMutableData(capacity: BUFFER_MAX) 326 | let buffer = UnsafeMutablePointer(buf!.bytes) 327 | let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) 328 | if length > 0 { 329 | if !connected { 330 | let status = processHTTP(buffer, bufferLen: length) 331 | if !status { 332 | self.doDisconnect(self.errorWithDetail("Invalid HTTP upgrade", code: 1)) 333 | } 334 | } else { 335 | var process = false 336 | if inputQueue.count == 0 { 337 | process = true 338 | } 339 | inputQueue.append(NSData(bytes: buffer, length: length)) 340 | if process { 341 | dequeueInput() 342 | } 343 | } 344 | } 345 | } 346 | ///dequeue the incoming input so it is processed in order 347 | private func dequeueInput() { 348 | if inputQueue.count > 0 { 349 | let data = inputQueue[0] 350 | var work = data 351 | if (fragBuffer != nil) { 352 | let combine = NSMutableData(data: fragBuffer!) 353 | combine.appendData(data) 354 | work = combine 355 | fragBuffer = nil 356 | } 357 | let buffer = UnsafePointer(work.bytes) 358 | processRawMessage(buffer, bufferLen: work.length) 359 | inputQueue = inputQueue.filter{$0 != data} 360 | dequeueInput() 361 | } 362 | } 363 | ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. 364 | private func processHTTP(buffer: UnsafePointer, bufferLen: Int) -> Bool { 365 | let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] 366 | var k = 0 367 | var totalSize = 0 368 | for var i = 0; i < bufferLen; i++ { 369 | if buffer[i] == CRLFBytes[k] { 370 | k++ 371 | if k == 3 { 372 | totalSize = i + 1 373 | break 374 | } 375 | } else { 376 | k = 0 377 | } 378 | } 379 | if totalSize > 0 { 380 | if validateResponse(buffer, bufferLen: totalSize) { 381 | dispatch_async(queue, { 382 | self.connected = true 383 | if let connectBlock = self.onConnect { 384 | connectBlock() 385 | } 386 | self.delegate?.websocketDidConnect(self) 387 | }) 388 | totalSize += 1 //skip the last \n 389 | let restSize = bufferLen - totalSize 390 | if restSize > 0 { 391 | processRawMessage((buffer+totalSize), bufferLen: restSize) 392 | } 393 | return true 394 | } 395 | } 396 | return false 397 | } 398 | 399 | ///validates the HTTP is a 101 as per the RFC spec 400 | private func validateResponse(buffer: UnsafePointer, bufferLen: Int) -> Bool { 401 | let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() 402 | CFHTTPMessageAppendBytes(response, buffer, bufferLen) 403 | if CFHTTPMessageGetResponseStatusCode(response) != 101 { 404 | return false 405 | } 406 | let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) 407 | let headers:NSDictionary? = cfHeaders?.takeRetainedValue() 408 | let acceptKey = headers?[headerWSAcceptName] as! NSString 409 | if acceptKey.length > 0 { 410 | return true 411 | } 412 | return false 413 | } 414 | 415 | ///process the websocket data 416 | private func processRawMessage(buffer: UnsafePointer, bufferLen: Int) { 417 | let response = readStack.last 418 | if response != nil && bufferLen < 2 { 419 | fragBuffer = NSData(bytes: buffer, length: bufferLen) 420 | return 421 | } 422 | if response != nil && response!.bytesLeft > 0 { 423 | let resp = response! 424 | var len = resp.bytesLeft 425 | var extra = bufferLen - resp.bytesLeft 426 | if resp.bytesLeft > bufferLen { 427 | len = bufferLen 428 | extra = 0 429 | } 430 | resp.bytesLeft -= len 431 | resp.buffer?.appendData(NSData(bytes: buffer, length: len)) 432 | processResponse(resp) 433 | let offset = bufferLen - extra 434 | if extra > 0 { 435 | processExtra((buffer+offset), bufferLen: extra) 436 | } 437 | return 438 | } else { 439 | let isFin = (FinMask & buffer[0]) 440 | let receivedOpcode = (OpCodeMask & buffer[0]) 441 | let isMasked = (MaskMask & buffer[1]) 442 | let payloadLen = (PayloadLenMask & buffer[1]) 443 | var offset = 2 444 | if((isMasked > 0 || (RSVMask & buffer[0]) > 0) && receivedOpcode != OpCode.Pong.rawValue) { 445 | let errCode = CloseCode.ProtocolError.rawValue 446 | let error = self.errorWithDetail("masked and rsv data is not currently supported", code: errCode) 447 | self.doDisconnect(error) 448 | writeError(errCode) 449 | return 450 | } 451 | let isControlFrame = (receivedOpcode == OpCode.ConnectionClose.rawValue || receivedOpcode == OpCode.Ping.rawValue) 452 | if !isControlFrame && (receivedOpcode != OpCode.BinaryFrame.rawValue && receivedOpcode != OpCode.ContinueFrame.rawValue && 453 | receivedOpcode != OpCode.TextFrame.rawValue && receivedOpcode != OpCode.Pong.rawValue) { 454 | let errCode = CloseCode.ProtocolError.rawValue 455 | let error = self.errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode) 456 | self.doDisconnect(error) 457 | writeError(errCode) 458 | return 459 | } 460 | if isControlFrame && isFin == 0 { 461 | let errCode = CloseCode.ProtocolError.rawValue 462 | let error = self.errorWithDetail("control frames can't be fragmented", code: errCode) 463 | self.doDisconnect(error) 464 | writeError(errCode) 465 | return 466 | } 467 | if receivedOpcode == OpCode.ConnectionClose.rawValue { 468 | var code = CloseCode.Normal.rawValue 469 | if payloadLen == 1 { 470 | code = CloseCode.ProtocolError.rawValue 471 | } else if payloadLen > 1 { 472 | let codeBuffer = UnsafePointer((buffer+offset)) 473 | code = codeBuffer[0].bigEndian 474 | if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) { 475 | code = CloseCode.ProtocolError.rawValue 476 | } 477 | offset += 2 478 | } 479 | if payloadLen > 2 { 480 | let len = Int(payloadLen-2) 481 | if len > 0 { 482 | let bytes = UnsafePointer((buffer+offset)) 483 | let str: NSString? = NSString(data: NSData(bytes: bytes, length: len), encoding: NSUTF8StringEncoding) 484 | if str == nil { 485 | code = CloseCode.ProtocolError.rawValue 486 | } 487 | } 488 | } 489 | let error = self.errorWithDetail("connection closed by server", code: code) 490 | self.doDisconnect(error) 491 | writeError(code) 492 | return 493 | } 494 | if isControlFrame && payloadLen > 125 { 495 | writeError(CloseCode.ProtocolError.rawValue) 496 | return 497 | } 498 | var dataLength = UInt64(payloadLen) 499 | if dataLength == 127 { 500 | let bytes = UnsafePointer((buffer+offset)) 501 | dataLength = bytes[0].bigEndian 502 | offset += sizeof(UInt64) 503 | } else if dataLength == 126 { 504 | let bytes = UnsafePointer((buffer+offset)) 505 | dataLength = UInt64(bytes[0].bigEndian) 506 | offset += sizeof(UInt16) 507 | } 508 | if bufferLen < offset || UInt64(bufferLen - offset) < dataLength { 509 | fragBuffer = NSData(bytes: buffer, length: bufferLen) 510 | return 511 | } 512 | var len = dataLength 513 | if dataLength > UInt64(bufferLen) { 514 | len = UInt64(bufferLen-offset) 515 | } 516 | var data: NSData! 517 | if len < 0 { 518 | len = 0 519 | data = NSData() 520 | } else { 521 | data = NSData(bytes: UnsafePointer((buffer+offset)), length: Int(len)) 522 | } 523 | if receivedOpcode == OpCode.Pong.rawValue { 524 | dispatch_async(queue, { 525 | self.onPong?() 526 | self.pongDelegate?.websocketDidReceivePong(self) 527 | }) 528 | 529 | let step = Int(offset+numericCast(len)) 530 | let extra = bufferLen-step 531 | if extra > 0 { 532 | processRawMessage((buffer+step), bufferLen: extra) 533 | } 534 | return 535 | } 536 | var response = readStack.last 537 | if isControlFrame { 538 | response = nil //don't append pings 539 | } 540 | if isFin == 0 && receivedOpcode == OpCode.ContinueFrame.rawValue && response == nil { 541 | let errCode = CloseCode.ProtocolError.rawValue 542 | let error = self.errorWithDetail("continue frame before a binary or text frame", code: errCode) 543 | self.doDisconnect(error) 544 | writeError(errCode) 545 | return 546 | } 547 | var isNew = false 548 | if(response == nil) { 549 | if receivedOpcode == OpCode.ContinueFrame.rawValue { 550 | let errCode = CloseCode.ProtocolError.rawValue 551 | let error = self.errorWithDetail("first frame can't be a continue frame", 552 | code: errCode) 553 | self.doDisconnect(error) 554 | writeError(errCode) 555 | return 556 | } 557 | isNew = true 558 | response = WSResponse() 559 | response!.code = OpCode(rawValue: receivedOpcode)! 560 | response!.bytesLeft = Int(dataLength) 561 | response!.buffer = NSMutableData(data: data) 562 | } else { 563 | if receivedOpcode == OpCode.ContinueFrame.rawValue { 564 | response!.bytesLeft = Int(dataLength) 565 | } else { 566 | let errCode = CloseCode.ProtocolError.rawValue 567 | let error = self.errorWithDetail("second and beyond of fragment message must be a continue frame", 568 | code: errCode) 569 | self.doDisconnect(error) 570 | writeError(errCode) 571 | return 572 | } 573 | response!.buffer!.appendData(data) 574 | } 575 | if response != nil { 576 | response!.bytesLeft -= Int(len) 577 | response!.frameCount++ 578 | response!.isFin = isFin > 0 ? true : false 579 | if(isNew) { 580 | readStack.append(response!) 581 | } 582 | processResponse(response!) 583 | } 584 | 585 | let step = Int(offset+numericCast(len)) 586 | let extra = bufferLen-step 587 | if(extra > 0) { 588 | processExtra((buffer+step), bufferLen: extra) 589 | } 590 | } 591 | 592 | } 593 | 594 | ///process the extra of a buffer 595 | private func processExtra(buffer: UnsafePointer, bufferLen: Int) { 596 | if bufferLen < 2 { 597 | fragBuffer = NSData(bytes: buffer, length: bufferLen) 598 | } else { 599 | processRawMessage(buffer, bufferLen: bufferLen) 600 | } 601 | } 602 | 603 | ///process the finished response of a buffer 604 | private func processResponse(response: WSResponse) -> Bool { 605 | if response.isFin && response.bytesLeft <= 0 { 606 | if response.code == .Ping { 607 | let data = response.buffer! //local copy so it is perverse for writing 608 | dequeueWrite(data, code: OpCode.Pong) 609 | } else if response.code == .TextFrame { 610 | let str: NSString? = NSString(data: response.buffer!, encoding: NSUTF8StringEncoding) 611 | 612 | if let str = str as String? { 613 | dispatch_async(queue, { 614 | self.onText?(str) 615 | self.delegate?.websocketDidReceiveMessage(self, text: str) 616 | }) 617 | } else { 618 | writeError(CloseCode.Encoding.rawValue) 619 | return false 620 | } 621 | } else if response.code == .BinaryFrame { 622 | let data = response.buffer! //local copy so it is perverse for writing 623 | dispatch_async(queue) { 624 | self.onData?(data) 625 | self.delegate?.websocketDidReceiveData(self, data: data) 626 | } 627 | } 628 | 629 | readStack.removeLast() 630 | return true 631 | } 632 | return false 633 | } 634 | 635 | ///Create an error 636 | private func errorWithDetail(detail: String, code: UInt16) -> NSError { 637 | var details = Dictionary() 638 | details[NSLocalizedDescriptionKey] = detail 639 | return NSError(domain: "Websocket", code: Int(code), userInfo: details) 640 | } 641 | 642 | ///write a an error to the socket 643 | private func writeError(code: UInt16) { 644 | let buf = NSMutableData(capacity: sizeof(UInt16)) 645 | let buffer = UnsafeMutablePointer(buf!.bytes) 646 | buffer[0] = code.bigEndian 647 | dequeueWrite(NSData(bytes: buffer, length: sizeof(UInt16)), code: .ConnectionClose) 648 | } 649 | ///used to write things to the stream 650 | private func dequeueWrite(data: NSData, code: OpCode) { 651 | if writeQueue == nil { 652 | writeQueue = NSOperationQueue() 653 | writeQueue!.maxConcurrentOperationCount = 1 654 | } 655 | writeQueue!.addOperationWithBlock { 656 | //stream isn't ready, let's wait 657 | var tries = 0; 658 | while self.outputStream == nil || !self.connected { 659 | if(tries < 5) { 660 | sleep(1); 661 | } else { 662 | break; 663 | } 664 | tries++; 665 | } 666 | if !self.connected { 667 | return 668 | } 669 | var offset = 2 670 | UINT16_MAX 671 | let bytes = UnsafeMutablePointer(data.bytes) 672 | let dataLength = data.length 673 | let frame = NSMutableData(capacity: dataLength + self.MaxFrameSize) 674 | let buffer = UnsafeMutablePointer(frame!.mutableBytes) 675 | buffer[0] = self.FinMask | code.rawValue 676 | if dataLength < 126 { 677 | buffer[1] = CUnsignedChar(dataLength) 678 | } else if dataLength <= Int(UInt16.max) { 679 | buffer[1] = 126 680 | let sizeBuffer = UnsafeMutablePointer((buffer+offset)) 681 | sizeBuffer[0] = UInt16(dataLength).bigEndian 682 | offset += sizeof(UInt16) 683 | } else { 684 | buffer[1] = 127 685 | let sizeBuffer = UnsafeMutablePointer((buffer+offset)) 686 | sizeBuffer[0] = UInt64(dataLength).bigEndian 687 | offset += sizeof(UInt64) 688 | } 689 | buffer[1] |= self.MaskMask 690 | let maskKey = UnsafeMutablePointer(buffer + offset) 691 | SecRandomCopyBytes(kSecRandomDefault, Int(sizeof(UInt32)), maskKey) 692 | offset += sizeof(UInt32) 693 | 694 | for (var i = 0; i < dataLength; i++) { 695 | buffer[offset] = bytes[i] ^ maskKey[i % sizeof(UInt32)] 696 | offset += 1 697 | } 698 | var total = 0 699 | while true { 700 | if self.outputStream == nil { 701 | break 702 | } 703 | let writeBuffer = UnsafePointer(frame!.bytes+total) 704 | let len = self.outputStream?.write(writeBuffer, maxLength: offset-total) 705 | if len == nil || len! < 0 { 706 | var error: NSError? 707 | if let streamError = self.outputStream?.streamError { 708 | error = streamError 709 | } else { 710 | let errCode = InternalErrorCode.OutputStreamWriteError.rawValue 711 | error = self.errorWithDetail("output stream error during write", code: errCode) 712 | } 713 | self.doDisconnect(error) 714 | break 715 | } else { 716 | total += len! 717 | } 718 | if total >= offset { 719 | break 720 | } 721 | } 722 | 723 | } 724 | } 725 | 726 | ///used to preform the disconnect delegate 727 | private func doDisconnect(error: NSError?) { 728 | if !self.didDisconnect { 729 | dispatch_async(queue) { 730 | self.didDisconnect = true 731 | 732 | self.onDisconnect?(error) 733 | self.delegate?.websocketDidDisconnect(self, error: error) 734 | } 735 | } 736 | } 737 | 738 | } 739 | 740 | ////////////////////////////////////////////////////////////////////////////////////////////////// 741 | // 742 | // Security.swift 743 | // Starscream 744 | // 745 | // Created by Dalton Cherry on 5/16/15. 746 | // Copyright (c) 2015 Vluxe. All rights reserved. 747 | // 748 | ////////////////////////////////////////////////////////////////////////////////////////////////// 749 | 750 | import Foundation 751 | import Security 752 | 753 | private class SSLCert { 754 | var certData: NSData? 755 | var key: SecKeyRef? 756 | 757 | /** 758 | Designated init for certificates 759 | 760 | :param: data is the binary data of the certificate 761 | 762 | :returns: a representation security object to be used with 763 | */ 764 | init(data: NSData) { 765 | self.certData = data 766 | } 767 | 768 | /** 769 | Designated init for public keys 770 | 771 | :param: key is the public key to be used 772 | 773 | :returns: a representation security object to be used with 774 | */ 775 | init(key: SecKeyRef) { 776 | self.key = key 777 | } 778 | } 779 | 780 | private class Security { 781 | private var validatedDN = true //should the domain name be validated? 782 | 783 | var isReady = false //is the key processing done? 784 | var certificates: [NSData]? //the certificates 785 | var pubKeys: [SecKeyRef]? //the public keys 786 | var usePublicKeys = false //use public keys or certificate validation? 787 | 788 | /** 789 | Use certs from main app bundle 790 | 791 | :param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation 792 | 793 | :returns: a representation security object to be used with 794 | */ 795 | private convenience init(usePublicKeys: Bool = false) { 796 | let paths = NSBundle.mainBundle().pathsForResourcesOfType("cer", inDirectory: ".") 797 | var collect = Array() 798 | for path in paths { 799 | if let d = NSData(contentsOfFile: path as String) { 800 | collect.append(SSLCert(data: d)) 801 | } 802 | } 803 | self.init(certs:collect, usePublicKeys: usePublicKeys) 804 | } 805 | 806 | /** 807 | Designated init 808 | 809 | :param: keys is the certificates or public keys to use 810 | :param: usePublicKeys is to specific if the publicKeys or certificates should be used for SSL pinning validation 811 | 812 | :returns: a representation security object to be used with 813 | */ 814 | private init(certs: [SSLCert], usePublicKeys: Bool) { 815 | self.usePublicKeys = usePublicKeys 816 | 817 | if self.usePublicKeys { 818 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), { 819 | var collect = Array() 820 | for cert in certs { 821 | if let data = cert.certData where cert.key == nil { 822 | cert.key = self.extractPublicKey(data) 823 | } 824 | if let k = cert.key { 825 | collect.append(k) 826 | } 827 | } 828 | self.pubKeys = collect 829 | self.isReady = true 830 | }) 831 | } else { 832 | var collect = Array() 833 | for cert in certs { 834 | if let d = cert.certData { 835 | collect.append(d) 836 | } 837 | } 838 | self.certificates = collect 839 | self.isReady = true 840 | } 841 | } 842 | 843 | /** 844 | Valid the trust and domain name. 845 | 846 | :param: trust is the serverTrust to validate 847 | :param: domain is the CN domain to validate 848 | 849 | :returns: if the key was successfully validated 850 | */ 851 | private func isValid(trust: SecTrustRef, domain: String?) -> Bool { 852 | 853 | var tries = 0 854 | while(!self.isReady) { 855 | usleep(1000) 856 | tries += 1 857 | if tries > 5 { 858 | return false //doesn't appear it is going to ever be ready... 859 | } 860 | } 861 | var policy: SecPolicyRef 862 | if self.validatedDN { 863 | policy = SecPolicyCreateSSL(true, domain) 864 | } else { 865 | policy = SecPolicyCreateBasicX509() 866 | } 867 | SecTrustSetPolicies(trust,policy) 868 | if self.usePublicKeys { 869 | if let keys = self.pubKeys { 870 | var trustedCount = 0 871 | let serverPubKeys = publicKeyChainForTrust(trust) 872 | for serverKey in serverPubKeys as [AnyObject] { 873 | for key in keys as [AnyObject] { 874 | if serverKey.isEqual(key) { 875 | trustedCount++ 876 | break 877 | } 878 | } 879 | } 880 | if trustedCount == serverPubKeys.count { 881 | return true 882 | } 883 | } 884 | } else if let certs = self.certificates { 885 | let serverCerts = certificateChainForTrust(trust) 886 | var collect = Array() 887 | for cert in certs { 888 | collect.append(SecCertificateCreateWithData(nil,cert)!) 889 | } 890 | SecTrustSetAnchorCertificates(trust,collect) 891 | var result: SecTrustResultType = 0 892 | SecTrustEvaluate(trust,&result) 893 | let r = Int(result) 894 | if r == kSecTrustResultUnspecified || r == kSecTrustResultProceed { 895 | var trustedCount = 0 896 | for serverCert in serverCerts { 897 | for cert in certs { 898 | if cert == serverCert { 899 | trustedCount++ 900 | break 901 | } 902 | } 903 | } 904 | if trustedCount == serverCerts.count { 905 | return true 906 | } 907 | } 908 | } 909 | return false 910 | } 911 | 912 | /** 913 | Get the public key from a certificate data 914 | 915 | :param: data is the certificate to pull the public key from 916 | 917 | :returns: a public key 918 | */ 919 | func extractPublicKey(data: NSData) -> SecKeyRef? { 920 | let possibleCert = SecCertificateCreateWithData(nil,data) 921 | if let cert = possibleCert { 922 | return extractPublicKeyFromCert(cert,policy: SecPolicyCreateBasicX509()) 923 | } 924 | return nil 925 | } 926 | 927 | /** 928 | Get the public key from a certificate 929 | 930 | :param: data is the certificate to pull the public key from 931 | 932 | :returns: a public key 933 | */ 934 | func extractPublicKeyFromCert(cert: SecCertificate, policy: SecPolicy) -> SecKeyRef? { 935 | let possibleTrust = UnsafeMutablePointer.alloc(1) 936 | SecTrustCreateWithCertificates( cert, policy, possibleTrust) 937 | if let trust = possibleTrust.memory { 938 | var result: SecTrustResultType = 0 939 | SecTrustEvaluate(trust,&result) 940 | return SecTrustCopyPublicKey(trust) 941 | } 942 | return nil 943 | } 944 | 945 | /** 946 | Get the certificate chain for the trust 947 | 948 | :param: trust is the trust to lookup the certificate chain for 949 | 950 | :returns: the certificate chain for the trust 951 | */ 952 | func certificateChainForTrust(trust: SecTrustRef) -> Array { 953 | var collect = Array() 954 | for var i = 0; i < SecTrustGetCertificateCount(trust); i++ { 955 | let cert = SecTrustGetCertificateAtIndex(trust,i) 956 | collect.append(SecCertificateCopyData(cert!)) 957 | } 958 | return collect 959 | } 960 | 961 | /** 962 | Get the public key chain for the trust 963 | 964 | :param: trust is the trust to lookup the certificate chain and extract the public keys 965 | 966 | :returns: the public keys from the certifcate chain for the trust 967 | */ 968 | func publicKeyChainForTrust(trust: SecTrustRef) -> Array { 969 | var collect = Array() 970 | let policy = SecPolicyCreateBasicX509() 971 | for var i = 0; i < SecTrustGetCertificateCount(trust); i++ { 972 | let cert = SecTrustGetCertificateAtIndex(trust,i) 973 | if let key = extractPublicKeyFromCert(cert!, policy: policy) { 974 | collect.append(key) 975 | } 976 | } 977 | return collect 978 | } 979 | 980 | 981 | } -------------------------------------------------------------------------------- /examples/index.ios.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Copy and paste this code into your index.ios.js file. 5 | * Remember to update the app nape in the AppRegistry. 6 | * You will need a socket.io server running on port 3000. 7 | */ 8 | 9 | 10 | 'use strict'; 11 | 12 | var React = require('react-native'); 13 | var SocketIO = require('react-native-swift-socketio'); 14 | var { 15 | AppRegistry, 16 | StyleSheet, 17 | Text, 18 | View, 19 | Component, 20 | TouchableWithoutFeedback 21 | } = React; 22 | 23 | class RNSwiftSocketIOTest extends Component{ 24 | 25 | constructor () { 26 | this.socket = new SocketIO('localhost:3000', {}); 27 | this.state = { status: 'Not connected' }; 28 | } 29 | 30 | componentDidMount () { 31 | 32 | this.socket.on('connect', () => { 33 | this.setState({ 34 | status: 'Connected' 35 | }); 36 | }); 37 | 38 | this.socket.connect(); 39 | } 40 | 41 | emitEvent () { 42 | this.socket.emit('randomEvent', { 43 | some: 'data' 44 | }); 45 | } 46 | 47 | render () { 48 | return ( 49 | 50 | 51 | this.emitEvent()}> 52 | 53 | 54 | Emit an event 55 | 56 | 57 | 58 | 59 | 60 | Connection status: {this.state.status} 61 | 62 | 63 | 64 | ); 65 | } 66 | }; 67 | 68 | var styles = StyleSheet.create({ 69 | container: { 70 | flex: 1, 71 | justifyContent: 'center', 72 | alignItems: 'center', 73 | backgroundColor: '#F5FCFF', 74 | }, 75 | 76 | btn: { 77 | backgroundColor: '#4F67FF', 78 | padding: 30, 79 | borderRadius: 5 80 | }, 81 | 82 | btnText: { 83 | color: '#fff' 84 | } 85 | }); 86 | 87 | AppRegistry.registerComponent('RNSwiftSocketIOTest', () => RNSwiftSocketIOTest); 88 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react-native'); 4 | 5 | var { 6 | DeviceEventEmitter, 7 | SocketIO 8 | } = React; 9 | 10 | class Socket { 11 | constructor (host, config) { 12 | 13 | if(typeof host === 'undefined') 14 | throw 'Hello there! Could you please give socket a host, please.'; 15 | if(typeof config === 'undefined') 16 | config = {}; 17 | 18 | this.sockets = SocketIO; 19 | this.isConnected = false; 20 | this.handlers = {}; 21 | this.onAnyHandler = null; 22 | 23 | this.deviceEventSubscription = DeviceEventEmitter.addListener( 24 | 'socketEvent', this._handleEvent.bind(this) 25 | ); 26 | 27 | // Set default handlers 28 | this.defaultHandlers = { 29 | connect: () => { 30 | this.isConnected = true; 31 | }, 32 | 33 | disconnect: () => { 34 | this.isConnected = false; 35 | } 36 | }; 37 | 38 | // Set initial configuration 39 | this.sockets.initialise(host, config); 40 | } 41 | 42 | _handleEvent (event) { 43 | if(this.handlers.hasOwnProperty(event.name)) 44 | this.handlers[event.name]( 45 | (event.hasOwnProperty('items')) ? event.items : null 46 | ); 47 | 48 | if(this.defaultHandlers.hasOwnProperty(event.name)) 49 | this.defaultHandlers[event.name](); 50 | 51 | if(this.onAnyHandler) this.onAnyHandler(event); 52 | } 53 | 54 | connect () { 55 | this.sockets.connect(); 56 | } 57 | 58 | on (event, handler) { 59 | this.handlers[event] = handler; 60 | } 61 | 62 | onAny (handler) { 63 | this.onAnyHandler = handler; 64 | } 65 | 66 | emit (event, data) { 67 | this.sockets.emit(event, data); 68 | } 69 | 70 | joinNamespace () { 71 | this.sockets.joinNamespace(); 72 | } 73 | 74 | leaveNamespace () { 75 | this.sockets.leaveNamespace(); 76 | } 77 | 78 | close (fast) { 79 | if(typeof fast === 'undefined') fast = false; 80 | this.sockets.close(fast); 81 | } 82 | 83 | reconnect () { 84 | this.sockets.reconnect(); 85 | } 86 | } 87 | 88 | module.exports = Socket; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-swift-socketio", 3 | "version": "0.0.6", 4 | "description": "A react native wrapper for socket.io-client-swift", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/kirkness/react-native-swift-socketio.git" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "ios", 16 | "socket-io", 17 | "sockets", 18 | "swift", 19 | "real-time", 20 | "react-component" 21 | ], 22 | "author": "Henry Kirkness", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/kirkness/react-native-swift-socketio/issues" 26 | }, 27 | "homepage": "https://github.com/kirkness/react-native-swift-socketio", 28 | "devDependencies": { 29 | "react-native": "^0.4.2" 30 | } 31 | } 32 | --------------------------------------------------------------------------------