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