diff --git a/README.md b/README.md index 792c2b8..4e06a4e 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{ ``` ## Features -- Supports socket.io 1.0+ +- Supports socket.io 2.0+ (For socket.io 1.0 use v9.x) - Supports binary - Supports Polling and WebSockets - Supports TLS/SSL diff --git a/SocketIO-MacTests/SocketEngineTest.swift b/SocketIO-MacTests/SocketEngineTest.swift index b797e04..c8cc46d 100644 --- a/SocketIO-MacTests/SocketEngineTest.swift +++ b/SocketIO-MacTests/SocketEngineTest.swift @@ -17,28 +17,28 @@ class SocketEngineTest: XCTestCase { super.setUp() client = SocketIOClient(socketURL: URL(string: "http://localhost")!) engine = SocketEngine(client: client, url: URL(string: "http://localhost")!, options: nil) - + client.setTestable() } - + func testBasicPollingMessage() { let expect = expectation(description: "Basic polling test") client.on("blankTest") {data, ack in expect.fulfill() } - + engine.parsePollingMessage("15:42[\"blankTest\"]") waitForExpectations(timeout: 3, handler: nil) } - + func testTwoPacketsInOnePollTest() { let finalExpectation = expectation(description: "Final packet in poll test") var gotBlank = false - + client.on("blankTest") {data, ack in gotBlank = true } - + client.on("stringTest") {data, ack in if let str = data[0] as? String, gotBlank { if str == "hello" { @@ -46,44 +46,46 @@ class SocketEngineTest: XCTestCase { } } } - + engine.parsePollingMessage("15:42[\"blankTest\"]24:42[\"stringTest\",\"hello\"]") waitForExpectations(timeout: 3, handler: nil) } - + func testEngineDoesErrorOnUnknownTransport() { let finalExpectation = expectation(description: "Unknown Transport") - + client.on("error") {data, ack in if let error = data[0] as? String, error == "Unknown transport" { finalExpectation.fulfill() } } - - engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}", fromPolling: false) + + engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}") waitForExpectations(timeout: 3, handler: nil) } - + func testEngineDoesErrorOnUnknownMessage() { let finalExpectation = expectation(description: "Engine Errors") - + client.on("error") {data, ack in finalExpectation.fulfill() } - - engine.parseEngineMessage("afafafda", fromPolling: false) + + engine.parseEngineMessage("afafafda") waitForExpectations(timeout: 3, handler: nil) } - + func testEngineDecodesUTF8Properly() { let expect = expectation(description: "Engine Decodes utf8") - + client.on("stringTest") {data, ack in - XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo", "Failed string test") + XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo𦅙𦅛", "Failed string test") expect.fulfill() } - engine.parsePollingMessage("41:42[\"stringTest\",\"lïne one\\nlÄ«ne \\rtwo\"]") + let stringMessage = "42[\"stringTest\",\"lïne one\\nlīne \\rtwo𦅙𦅛\"]" + + engine.parsePollingMessage("\(stringMessage.utf16.count):\(stringMessage)") waitForExpectations(timeout: 3, handler: nil) } @@ -102,23 +104,23 @@ class SocketEngineTest: XCTestCase { XCTAssertEqual(engine.urlPolling.query, "transport=polling&b64=1&forbidden=%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23%5B%5D%22%20%7B%7D") XCTAssertEqual(engine.urlWebSocket.query, "transport=websocket&forbidden=%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23%5B%5D%22%20%7B%7D") } - + func testBase64Data() { let expect = expectation(description: "Engine Decodes base64 data") let b64String = "b4aGVsbG8NCg==" let packetString = "451-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]" - + client.on("test") {data, ack in if let data = data[0] as? Data, let string = String(data: data, encoding: .utf8) { XCTAssertEqual(string, "hello") } - + expect.fulfill() } - - engine.parseEngineMessage(packetString, fromPolling: false) - engine.parseEngineMessage(b64String, fromPolling: false) - + + engine.parseEngineMessage(packetString) + engine.parseEngineMessage(b64String) + waitForExpectations(timeout: 3, handler: nil) } } diff --git a/SocketIO-MacTests/SocketObjectiveCTest.m b/SocketIO-MacTests/SocketObjectiveCTest.m index e0edc8a..46848c6 100644 --- a/SocketIO-MacTests/SocketObjectiveCTest.m +++ b/SocketIO-MacTests/SocketObjectiveCTest.m @@ -7,7 +7,9 @@ // Merely tests whether the Objective-C api breaks // -#import +@import Dispatch; +@import Foundation; +@import XCTest; @import SocketIO; @interface SocketObjectiveCTest : XCTestCase @@ -24,20 +26,66 @@ self.socket = [[SocketIOClient alloc] initWithSocketURL:url config:@{@"log": @NO, @"forcePolling": @YES}]; } +- (void)testProperties { + NSURL* url = nil; + + url = self.socket.socketURL; + self.socket.forceNew = false; + self.socket.handleQueue = dispatch_get_main_queue(); + self.socket.nsp = @"/objective-c"; + self.socket.reconnects = false; + self.socket.reconnectWait = 1; +} + - (void)testOnSyntax { [self.socket on:@"someCallback" callback:^(NSArray* data, SocketAckEmitter* ack) { [ack with:@[@1]]; }]; } +- (void)testConnectSyntax { + [self.socket connect]; +} + +- (void)testConnectTimeoutAfterSyntax { + [self.socket connectWithTimeoutAfter:1 withHandler: ^() { }]; +} + +- (void)testDisconnectSyntax { + [self.socket disconnect]; +} + +- (void)testLeaveNamespaceSyntax { + [self.socket leaveNamespace]; +} + +- (void)testJoinNamespaceSyntax { + [self.socket joinNamespace:@"/objective-c"]; +} + +- (void)testOnAnySyntax { + [self.socket onAny:^(SocketAnyEvent* any) { + NSString* event = any.event; + NSArray* data = any.items; + + [self.socket emit:event with:data]; + }]; +} + +- (void)testReconnectSyntax { + [self.socket reconnect]; +} + +- (void)testRemoveAllHandlersSyntax { + [self.socket removeAllHandlers]; +} + - (void)testEmitSyntax { [self.socket emit:@"testEmit" with:@[@YES]]; } - (void)testEmitWithAckSyntax { - [[self.socket emitWithAck:@"testAckEmit" with:@[@YES]] timingOutAfter:0 callback:^(NSArray* data) { - - }]; + [[self.socket emitWithAck:@"testAckEmit" with:@[@YES]] timingOutAfter:0 callback:^(NSArray* data) { }]; } - (void)testOffSyntax { diff --git a/Source/SocketEngine.swift b/Source/SocketEngine.swift index c3114d1..5c1f841 100644 --- a/Source/SocketEngine.swift +++ b/Source/SocketEngine.swift @@ -66,10 +66,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// An array of HTTPCookies that are sent during the connection. public private(set) var cookies: [HTTPCookie]? - /// Set to `true` if using the node.js version of socket.io. The node.js version of socket.io - /// handles utf8 incorrectly. - public private(set) var doubleEncodeUTF8 = true - /// A dictionary of extra http headers that will be set during connection. public private(set) var extraHeaders: [String: String]? @@ -153,8 +149,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll 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): @@ -227,8 +221,8 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll // binary in base64 string let noPrefix = message[message.index(message.startIndex, offsetBy: 2).. URLRequest { - var req = req - + private func addHeaders(to req: inout URLRequest) { if cookies != nil { let headers = HTTPCookie.requestHeaderFields(with: cookies!) req.allHTTPHeaderFields = headers @@ -84,8 +82,6 @@ extension SocketEnginePollable { req.setValue(value, forHTTPHeaderField: headerName) } } - - return req } func createRequestForPostWithPostWait() -> URLRequest { @@ -94,9 +90,7 @@ extension SocketEnginePollable { var postStr = "" for packet in postWait { - let len = packet.characters.count - - postStr += "\(len):\(packet)" + postStr += "\(packet.utf16.count):\(packet)" } DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr) @@ -104,11 +98,10 @@ extension SocketEnginePollable { var req = URLRequest(url: urlPollingWithSid) let postData = postStr.data(using: .utf8, allowLossyConversion: false)! - req = addHeaders(for: req) + addHeaders(to: &req) req.httpMethod = "POST" req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") - req.httpBody = postData req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") @@ -119,20 +112,16 @@ extension SocketEnginePollable { /// /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. public func doPoll() { - if websocket || waitingForPoll || !connected || closed { - return - } + guard !websocket && !waitingForPoll && connected && !closed else { return } var req = URLRequest(url: urlPollingWithSid) - req = addHeaders(for: req) + addHeaders(to: &req) - doLongPoll(for: req ) + doLongPoll(for: req) } func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> Void) { - if !polling || closed || invalidated || fastUpgrade { - return - } + guard polling && !closed && !invalidated && !fastUpgrade else { return } DefaultSocketLogger.Logger.log("Doing polling %@ %@", type: "SocketEnginePolling", args: req.httpMethod ?? "", req) @@ -173,10 +162,10 @@ extension SocketEnginePollable { } private func flushWaitingForPost() { - if postWait.count == 0 || !connected { - return - } else if websocket { + guard postWait.count != 0 && connected else { return } + guard !websocket else { flushWaitingForPostToWebSocket() + return } @@ -211,13 +200,15 @@ extension SocketEnginePollable { func parsePollingMessage(_ str: String) { guard str.characters.count != 1 else { return } + DefaultSocketLogger.Logger.log("Got poll message: %@", type: "SocketEnginePolling", args: str) + var reader = SocketStringReader(message: str) while reader.hasNext { if let n = Int(reader.readUntilOccurence(of: ":")) { - parseEngineMessage(reader.read(count: n), fromPolling: true) + parseEngineMessage(reader.read(count: n)) } else { - parseEngineMessage(str, fromPolling: true) + parseEngineMessage(str) break } } @@ -232,15 +223,8 @@ extension SocketEnginePollable { /// - parameter withData: The data associated with this message. public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue) - let fixedMessage: String - if doubleEncodeUTF8 { - fixedMessage = doubleEncodeUTF8(message) - } else { - fixedMessage = message - } - - postWait.append(String(type.rawValue) + fixedMessage) + postWait.append(String(type.rawValue) + message) for data in datas { if case let .right(bin) = createBinaryDataForSend(using: data) { diff --git a/Source/SocketEngineSpec.swift b/Source/SocketEngineSpec.swift index a70d99d..71c9dc1 100644 --- a/Source/SocketEngineSpec.swift +++ b/Source/SocketEngineSpec.swift @@ -39,10 +39,6 @@ import Foundation /// The connect parameters sent during a connect. var connectParams: [String: Any]? { get set } - /// Set to `true` if using the node.js version of socket.io. The node.js version of socket.io - /// handles utf8 incorrectly. - var doubleEncodeUTF8: Bool { get } - /// An array of HTTPCookies that are sent during the connection. var cookies: [HTTPCookie]? { get } @@ -125,7 +121,7 @@ import Foundation /// - parameter message: The message to parse. /// - parameter fromPolling: Whether this message is from long-polling. /// If `true` we might have to fix utf8 encoding. - func parseEngineMessage(_ message: String, fromPolling: Bool) + func parseEngineMessage(_ message: String) /// Writes a message to engine.io, independent of transport. /// @@ -165,24 +161,6 @@ extension SocketEngineSpec { } } - func doubleEncodeUTF8(_ string: String) -> String { - if let latin1 = string.data(using: String.Encoding.utf8), - let utf8 = NSString(data: latin1, encoding: String.Encoding.isoLatin1.rawValue) { - return utf8 as String - } else { - return string - } - } - - func fixDoubleUTF8(_ string: String) -> String { - if let utf8 = string.data(using: String.Encoding.isoLatin1), - let latin1 = NSString(data: utf8, encoding: String.Encoding.utf8.rawValue) { - return latin1 as String - } else { - return string - } - } - /// Send an engine message (4) func send(_ msg: String, withData datas: [Data]) { write(msg, withType: .message, withData: datas) diff --git a/Source/SocketEngineWebsocket.swift b/Source/SocketEngineWebsocket.swift index 64265c4..3a76a9d 100644 --- a/Source/SocketEngineWebsocket.swift +++ b/Source/SocketEngineWebsocket.swift @@ -68,7 +68,7 @@ extension SocketEngineWebsocket { /// Delegate method for when a message is received. public func websocketDidReceiveMessage(socket: WebSocket, text: String) { - parseEngineMessage(text, fromPolling: false) + parseEngineMessage(text) } /// Delegate method for when binary is received. diff --git a/Source/SocketExtensions.swift b/Source/SocketExtensions.swift index bf5280a..94aca5f 100644 --- a/Source/SocketExtensions.swift +++ b/Source/SocketExtensions.swift @@ -48,8 +48,6 @@ extension NSDictionary { return .connectParams(params) case let ("cookies", cookies as [HTTPCookie]): 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): @@ -88,39 +86,39 @@ extension NSDictionary { return nil } } - + func toSocketConfiguration() -> SocketIOClientConfiguration { var options = [] as SocketIOClientConfiguration - + for (rawKey, value) in self { if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) { options.insert(opt) } } - + return options } } extension String { func toArray() throws -> [Any] { - guard let stringData = data(using: .utf8, allowLossyConversion: false) else { return [] } + guard let stringData = data(using: .utf16, allowLossyConversion: false) else { return [] } guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { throw JSONError.notArray } - + return array } - + func toNSDictionary() throws -> NSDictionary { - guard let binData = data(using: .utf8, allowLossyConversion: false) else { return [:] } + guard let binData = data(using: .utf16, allowLossyConversion: false) else { return [:] } guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else { throw JSONError.notNSDictionary } - + return json } - + func urlEncode() -> String? { return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) } diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index 8cb0bde..a0c92aa 100644 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -237,7 +237,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So /// - parameter items: The items to send with this event. May be left out. open func emit(_ event: String, _ items: SocketData...) { do { - emit(event, with: try items.map({ try $0.socketRepresentation() })) + try emit(event, with: items.map({ try $0.socketRepresentation() })) } catch let err { DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)", type: logType) diff --git a/Source/SocketIOClientOption.swift b/Source/SocketIOClientOption.swift index 119a71c..f594b6b 100644 --- a/Source/SocketIOClientOption.swift +++ b/Source/SocketIOClientOption.swift @@ -36,8 +36,8 @@ public enum SocketIOClientOption : ClientOption { /// An array of cookies that will be sent during the initial connection. case cookies([HTTPCookie]) - /// The node.js socket.io currently does funky things to unicode when doing HTTP long-polling. Passing `true` in - /// this option causes the client to try and fix any bad unicode that might be sent. + /// Deprecated + @available(*, deprecated, message: "No longer needed in socket.io 2.0+") case doubleEncodeUTF8(Bool) /// Any extra HTTP headers that should be sent during the initial connection. diff --git a/Source/SocketParsable.swift b/Source/SocketParsable.swift index 28b431d..5c698dc 100644 --- a/Source/SocketParsable.swift +++ b/Source/SocketParsable.swift @@ -107,7 +107,7 @@ extension SocketParsable where Self: SocketIOClientSpec { } } - var dataArray = message[message.characters.index(reader.currentIndex, offsetBy: 1).. String.Index { - currentIndex = message.characters.index(currentIndex, offsetBy: by) - + mutating func advance(by: Int) -> String.UTF16View.Index { + currentIndex = message.utf16.index(currentIndex, offsetBy: by) + return currentIndex } - + mutating func read(count: Int) -> String { - let readString = message[currentIndex.. String { - let substring = message[currentIndex.. String { - return read(count: message.characters.distance(from: currentIndex, to: message.endIndex)) + return read(count: message.utf16.distance(from: currentIndex, to: message.utf16.endIndex)) } }