├── .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 |
--------------------------------------------------------------------------------