diff --git a/README.md b/README.md index c625a9c..f9867e0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Socket.IO-client for iOS/OS X. ##Example ```swift -let socket = SocketIOClient(socketURL: "localhost:8080", options: [.Log(true), .ForcePolling(true)]) +let socket = SocketIOClient(socketURL: NSURL(string: "http://localhost:8080")!, options: [.Log(true), .ForcePolling(true)]) socket.on("connect") {data, ack in print("socket connected") @@ -26,7 +26,8 @@ socket.connect() ##Objective-C Example ```objective-c -SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8080" options:@{@"log": @YES, @"forcePolling": @YES}]; +NSURL* url = [[NSURL alloc] initWithString:@"http://localhost:8080"]; +SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url options:@{@"log": @YES, @"forcePolling": @YES}]; [socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) { NSLog(@"socket connected"); @@ -136,9 +137,9 @@ Run `seed install`. ##API Constructors ----------- -`init(var socketURL: String, options: Set = [])` - Creates a new SocketIOClient. opts is a Set of SocketIOClientOption. If your socket.io server is secure, you need to specify `https` in your socketURL. +`init(var socketURL: NSURL, options: Set = [])` - Creates a new SocketIOClient. options is a Set of SocketIOClientOption. If your socket.io server is secure, you need to specify `https` in your socketURL. -`convenience init(socketURL: String, options: NSDictionary?)` - Same as above, but meant for Objective-C. See Options on how convert between SocketIOClientOptions and dictionary keys. +`convenience init(socketURL: NSURL, options: NSDictionary?)` - Same as above, but meant for Objective-C. See Options on how convert between SocketIOClientOptions and dictionary keys. Options ------- diff --git a/SocketIO-MacTests/SocketEngineTest.swift b/SocketIO-MacTests/SocketEngineTest.swift index 4572a0f..90ef328 100644 --- a/SocketIO-MacTests/SocketEngineTest.swift +++ b/SocketIO-MacTests/SocketEngineTest.swift @@ -15,8 +15,8 @@ class SocketEngineTest: XCTestCase { override func setUp() { super.setUp() - client = SocketIOClient(socketURL: "") - engine = SocketEngine(client: client, url: "", options: nil) + client = SocketIOClient(socketURL: NSURL(string: "http://localhost")!) + engine = SocketEngine(client: client, url: NSURL(string: "http://localhost")!, options: nil) client.setTestable() } diff --git a/SocketIO-MacTests/SocketParserTest.swift b/SocketIO-MacTests/SocketParserTest.swift index 9c24913..d17bb44 100644 --- a/SocketIO-MacTests/SocketParserTest.swift +++ b/SocketIO-MacTests/SocketParserTest.swift @@ -10,7 +10,7 @@ import XCTest @testable import SocketIOClientSwift class SocketParserTest: XCTestCase { - let testSocket = SocketIOClient(socketURL: "") + let testSocket = SocketIOClient(socketURL: NSURL()) //Format key: message; namespace-data-binary-id static let packetTypes: Dictionary = [ diff --git a/SocketIO-MacTests/SocketSideEffectTest.swift b/SocketIO-MacTests/SocketSideEffectTest.swift index e703335..4580259 100644 --- a/SocketIO-MacTests/SocketSideEffectTest.swift +++ b/SocketIO-MacTests/SocketSideEffectTest.swift @@ -16,7 +16,7 @@ class SocketSideEffectTest: XCTestCase { override func setUp() { super.setUp() - socket = SocketIOClient(socketURL: "") + socket = SocketIOClient(socketURL: NSURL()) socket.setTestable() } diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index 761ff12..99f5b43 100644 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -29,6 +29,11 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb 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 @@ -46,9 +51,9 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb 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 = "" - public private(set) var urlWebSocket = "" + 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? @@ -59,11 +64,9 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) private typealias ProbeWaitQueue = [Probe] - private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet private let logType = "SocketEngine" - private let url: String + private let url: NSURL - private var connectParams: [String: AnyObject]? private var pingInterval: Double? private var pingTimeout = 0.0 { didSet { @@ -76,13 +79,15 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb private var secure = false private var selfSigned = false private var voipEnabled = false - - public init(client: SocketEngineClient, url: String, options: Set) { + + 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 .SessionDelegate(delegate): sessionDelegate = delegate case let .ForcePolling(force): @@ -105,11 +110,26 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb continue } } + + super.init() + + (urlPolling, urlWebSocket) = createURLs() } - public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) { - self.init(client: client, url: url, - options: options?.toSocketOptionsSet() ?? []) + public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) { + self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) + } + + @available(*, deprecated=5.3) + public convenience init(client: SocketEngineClient, urlString: String, options: Set) { + guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } + self.init(client: client, url: url, options: options) + } + + @available(*, deprecated=5.3) + public convenience init(client: SocketEngineClient, urlString: String, options: NSDictionary?) { + guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") } + self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? []) } deinit { @@ -131,7 +151,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb switch code { case 0: // Unknown transport didError(error) - case 1: // Unknown sid. clear and retry connect + case 1: // Unknown sid. didError(error) case 2: // Bad handshake request didError(error) @@ -188,49 +208,45 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb } } - private func createURLs(params: [String: AnyObject]?) -> (String, String) { + private func createURLs() -> (NSURL, NSURL) { if client == nil { - return ("", "") + return (NSURL(), NSURL()) } - let socketURL = "\(url)\(socketPath)/?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 + urlWebSocket.query = "transport=websocket" + urlPolling.query = "transport=polling&b64=1" if secure { - urlPolling = "https://" + socketURL + "polling" - urlWebSocket = "wss://" + socketURL + "websocket" + urlPolling.scheme = "https" + urlWebSocket.scheme = "wss" } else { - urlPolling = "http://" + socketURL + "polling" - urlWebSocket = "ws://" + socketURL + "websocket" + urlPolling.scheme = "http" + urlWebSocket.scheme = "ws" } - 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)" - } + if connectParams != nil { + for (key, value) in connectParams! { + queryString += "&\(key)=\(value)" } } - return (urlPolling, urlWebSocket) + urlWebSocket.query = urlWebSocket.query! + queryString + urlPolling.query = urlPolling.query! + queryString + + return (urlPolling.URL!, urlWebSocket.URL!) } private func createWebsocketAndConnect() { - let wsUrl = urlWebSocket + (sid == "" ? "" : "&sid=\(sid)") - - ws = WebSocket(url: NSURL(string: wsUrl)!) + let component = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)! + component.query = component.query! + (sid == "" ? "" : "&sid=\(sid)") + + ws = WebSocket(url: component.URL!) if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) @@ -363,22 +379,18 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb } } - public func open(opts: [String: AnyObject]?) { + public func open() { if connected { DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. " + "Abandoning open attempt", type: logType) return } - connectParams = opts - DefaultSocketLogger.Logger.log("Starting engine", type: logType) DefaultSocketLogger.Logger.log("Handshaking", type: logType) resetEngine() - (urlPolling, urlWebSocket) = createURLs(opts) - if forceWebsockets { polling = false websocket = true @@ -386,7 +398,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb return } - let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) + let reqPolling = NSMutableURLRequest(URL: urlPolling) if cookies != nil { let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) diff --git a/Source/SocketEnginePollable.swift b/Source/SocketEnginePollable.swift index cd8e512..b871e2d 100644 --- a/Source/SocketEnginePollable.swift +++ b/Source/SocketEnginePollable.swift @@ -71,7 +71,7 @@ extension SocketEnginePollable { postWait.removeAll(keepCapacity: false) - let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)")!) + let req = NSMutableURLRequest(URL: urlPollingWithSid) addHeaders(req) @@ -93,7 +93,7 @@ extension SocketEnginePollable { } waitingForPoll = true - let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)&b64=1")!) + let req = NSMutableURLRequest(URL: urlPollingWithSid) addHeaders(req) doLongPoll(req) @@ -112,7 +112,7 @@ extension SocketEnginePollable { func doLongPoll(req: NSURLRequest) { doRequest(req) {[weak self] data, res, err in - guard let this = self else {return} + guard let this = self else { return } if err != nil || data == nil { DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling") diff --git a/Source/SocketEngineSpec.swift b/Source/SocketEngineSpec.swift index 5a40a2e..1703d98 100644 --- a/Source/SocketEngineSpec.swift +++ b/Source/SocketEngineSpec.swift @@ -29,6 +29,7 @@ import Foundation weak var client: SocketEngineClient? { get set } var closed: Bool { get } var connected: Bool { get } + var connectParams: [String: AnyObject]? { get set } var cookies: [NSHTTPCookie]? { get } var extraHeaders: [String: String]? { get } var fastUpgrade: Bool { get } @@ -42,23 +43,30 @@ import Foundation var handleQueue: dispatch_queue_t! { get } var sid: String { get } var socketPath: String { get } - var urlPolling: String { get } - var urlWebSocket: String { get } + var urlPolling: NSURL { get } + var urlWebSocket: NSURL { get } var websocket: Bool { get } - init(client: SocketEngineClient, url: String, options: NSDictionary?) + init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) func close(reason: String) func didError(error: String) func doFastUpgrade() func flushWaitingForPostToWebSocket() - func open(opts: [String: AnyObject]?) + func open() 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.query = com.query! + "&sid=\(sid)" + + return com.URL! + } + func createBinaryDataForSend(data: NSData) -> Either { if websocket { var byteArray = [UInt8](count: 1, repeatedValue: 0x4) diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index 21b619d..c349202 100644 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -25,12 +25,11 @@ import Foundation public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable { - public let socketURL: String + public let socketURL: NSURL public private(set) var engine: SocketEngineSpec? public private(set) var status = SocketIOClientStatus.NotConnected - public var connectParams: [String: AnyObject]? public var forceNew = false public var nsp = "/" public var options: Set @@ -55,29 +54,20 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable private(set) var reconnectAttempts = -1 var waitingData = [SocketPacket]() - + /** Type safe way to create a new SocketIOClient. opts can be omitted */ - public init(socketURL: String, options: Set = []) { + public init(socketURL: NSURL, options: Set = []) { self.options = options - - if socketURL.hasPrefix("https://") { + self.socketURL = socketURL + + if socketURL.absoluteString.hasPrefix("https://") { self.options.insertIgnore(.Secure(true)) } - var cleanedURL = socketURL["https?://"] <~ "" - - if cleanedURL.hasSuffix("/") { - cleanedURL = String(cleanedURL.characters.dropLast()) - } - - self.socketURL = cleanedURL - for option in options { switch option { - case let .ConnectParams(params): - connectParams = params case let .Reconnects(reconnects): self.reconnects = reconnects case let .ReconnectAttempts(attempts): @@ -98,19 +88,32 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable continue } } - - self.options.insertIgnore(.Path("/socket.io")) - + + 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(var socketURL: String, options: Set)` + If using Swift it's recommended to use `init(socketURL: NSURL, options: Set)` */ - public convenience init(socketURL: String, options: NSDictionary?) { - self.init(socketURL: socketURL, - options: options?.toSocketOptionsSet() ?? []) + public convenience init(socketURL: NSURL, options: NSDictionary?) { + self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? []) + } + + /// Please use the NSURL based init + @available(*, deprecated=5.3) + public convenience init(socketURLString: String, options: Set = []) { + guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } + self.init(socketURL: url, options: options) + } + + /// Please use the NSURL based init + @available(*, deprecated=5.3) + public convenience init(socketURLString: String, options: NSDictionary?) { + guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") } + self.init(socketURL: url, options: options?.toSocketOptionsSet() ?? []) } deinit { @@ -158,9 +161,9 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable status = .Connecting if engine == nil || forceNew { - addEngine().open(connectParams) + addEngine().open() } else { - engine?.open(connectParams) + engine?.open() } guard timeoutAfter != 0 else { return } diff --git a/Source/SwiftRegex.swift b/Source/SwiftRegex.swift index 8ea2d0e..b7c134b 100644 --- a/Source/SwiftRegex.swift +++ b/Source/SwiftRegex.swift @@ -15,6 +15,7 @@ import Foundation infix operator <~ { associativity none precedence 130 } +private let lock = dispatch_semaphore_create(1) private var swiftRegexCache = [String: NSRegularExpression]() internal final class SwiftRegex: NSObject, BooleanType { @@ -22,6 +23,10 @@ internal final class SwiftRegex: NSObject, BooleanType { var regex: NSRegularExpression init(target:String, pattern:String, options:NSRegularExpressionOptions?) { + if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 { + fatalError("This should never happen") + } + self.target = target if let regex = swiftRegexCache[pattern] { self.regex = regex @@ -36,6 +41,7 @@ internal final class SwiftRegex: NSObject, BooleanType { self.regex = NSRegularExpression() } } + dispatch_semaphore_signal(lock) super.init() }