├── .gitignore ├── .swift-version ├── .travis.yml ├── CHANGELOG.md ├── Cartfile ├── Cartfile.resolved ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Socket.IO-Client-Swift.podspec ├── Socket.IO-Client-Swift.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── SocketIO.xcscheme ├── SocketIO ├── Info.plist └── SocketIO.h ├── Source └── SocketIO │ ├── Ack │ ├── SocketAckEmitter.swift │ └── SocketAckManager.swift │ ├── Client │ ├── SocketAnyEvent.swift │ ├── SocketEventHandler.swift │ ├── SocketIOClient.swift │ ├── SocketIOClientConfiguration.swift │ ├── SocketIOClientOption.swift │ ├── SocketIOClientSpec.swift │ ├── SocketIOStatus.swift │ └── SocketRawView.swift │ ├── Engine │ ├── SocketEngine.swift │ ├── SocketEngineClient.swift │ ├── SocketEnginePacketType.swift │ ├── SocketEnginePollable.swift │ ├── SocketEngineSpec.swift │ └── SocketEngineWebsocket.swift │ ├── Manager │ ├── SocketManager.swift │ └── SocketManagerSpec.swift │ ├── Parse │ ├── SocketPacket.swift │ └── SocketParsable.swift │ └── Util │ ├── SSLSecurity.swift │ ├── SocketExtensions.swift │ ├── SocketLogger.swift │ ├── SocketStringReader.swift │ └── SocketTypes.swift ├── Tests ├── TestSocketIO │ ├── SocketAckManagerTest.swift │ ├── SocketBasicPacketTest.swift │ ├── SocketEngineTest.swift │ ├── SocketIOClientConfigurationTest.swift │ ├── SocketMangerTest.swift │ ├── SocketNamespacePacketTest.swift │ ├── SocketParserTest.swift │ └── SocketSideEffectTest.swift └── TestSocketIOObjc │ ├── ManagerObjectiveCTest.h │ ├── ManagerObjectiveCTest.m │ ├── SocketObjectiveCTest.h │ └── SocketObjectiveCTest.m ├── Usage Docs ├── 12to13.md └── FAQ.md └── docs ├── 12to13.html ├── Classes.html ├── Classes ├── OnAckCallback.html ├── SSLSecurity.html ├── SocketAckEmitter.html ├── SocketAnyEvent.html ├── SocketClientManager.html ├── SocketEngine.html ├── SocketIOClient.html ├── SocketManager.html ├── SocketRawAckView.html └── SocketRawView.html ├── Enums.html ├── Enums ├── SocketAckStatus.html ├── SocketClientEvent.html ├── SocketEnginePacketType.html ├── SocketIOClientOption.html ├── SocketIOClientStatus.html ├── SocketIOStatus.html └── SocketParsableError.html ├── Guides.html ├── Protocols.html ├── Protocols ├── ConfigSettable.html ├── SocketData.html ├── SocketDataBufferable.html ├── SocketEngineClient.html ├── SocketEnginePollable.html ├── SocketEngineSpec.html ├── SocketEngineWebsocket.html ├── SocketIOClientSpec.html ├── SocketLogger.html ├── SocketManagerSpec.html └── SocketParsable.html ├── Structs.html ├── Structs ├── SocketEventHandler.html ├── SocketIOClientConfiguration.html ├── SocketPacket.html └── SocketPacket │ └── PacketType.html ├── Typealiases.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── faq.html ├── img ├── carat.png ├── dash.png ├── gh.png └── spinner.gif ├── index.html ├── js ├── jazzy.js ├── jazzy.search.js ├── jquery.min.js ├── lunr.min.js └── typeahead.jquery.js └── search.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | *.xcodeproj 5 | .build/* 6 | Packages/* 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | # Xcode 31 | build/ 32 | *.pbxuser 33 | !default.pbxuser 34 | *.mode1v3 35 | !default.mode1v3 36 | *.mode2v3 37 | !default.mode2v3 38 | *.perspectivev3 39 | !default.perspectivev3 40 | xcuserdata 41 | *.xccheckout 42 | *.moved-aside 43 | DerivedData 44 | *.hmap 45 | *.ipa 46 | *.xcuserstate 47 | 48 | Socket.IO-Test-Server/node_modules/* 49 | 50 | .idea/ 51 | docs/docsets/ 52 | docs/undocumented.json 53 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: Socket.IO-Client-Swift.xcodeproj # path to your xcodeproj folder 3 | xcode_scheme: SocketIO-Mac 4 | osx_image: xcode10 5 | branches: 6 | only: 7 | - master 8 | - development 9 | before_install: 10 | # - brew update 11 | # - brew outdated xctool || brew upgrade xctool 12 | # - brew outdated carthage || brew upgrade carthage 13 | - carthage update --platform macosx 14 | script: 15 | - xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO build test -quiet 16 | # - xcodebuild -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO build-for-testing -quiet 17 | # - xctool -project Socket.IO-Client-Swift.xcodeproj -scheme SocketIO run-tests --parallelize 18 | - swift test 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v13.3.1 2 | 3 | - Fixes various bugs. [#857](https://github.com/socketio/socket.io-client-swift/issues/857), [#1078](https://github.com/socketio/socket.io-client-swift/issues/1078) 4 | 5 | # v13.3.0 6 | 7 | - Copy cookies from polling to WebSockets ([#1057](https://github.com/socketio/socket.io-client-swift/issues/1057), [#1058](https://github.com/socketio/socket.io-client-swift/issues/1058)) 8 | 9 | # v13.2.1 10 | 11 | - Fix packets getting lost when WebSocket upgrade fails. [#1033](https://github.com/socketio/socket.io-client-swift/issues/1033) 12 | - Fix bad unit tests. [#794](https://github.com/socketio/socket.io-client-swift/issues/794) 13 | 14 | # v13.2.0 15 | 16 | - Add ability to bypass Data inspection in emits. [#992]((https://github.com/socketio/socket.io-client-swift/issues/992)) 17 | - Allow `SocketEngine` to be subclassed 18 | 19 | # v13.1.3 20 | 21 | - Fix setting reconnectAttempts [#989]((https://github.com/socketio/socket.io-client-swift/issues/989)) 22 | 23 | 24 | # v13.1.2 25 | 26 | - Fix [#950](https://github.com/socketio/socket.io-client-swift/issues/950) 27 | - Conforming to `SocketEngineWebsocket` no longer requires conforming to `WebsocketDelegate` 28 | 29 | 30 | # v13.1.1 31 | 32 | - Fix [#923](https://github.com/socketio/socket.io-client-swift/issues/923) 33 | - Fix [#894](https://github.com/socketio/socket.io-client-swift/issues/894) 34 | 35 | # v13.1.0 36 | 37 | - Allow setting `SocketEngineSpec.extraHeaders` after init. 38 | - Deprecate `SocketEngineSpec.websocket` in favor of just using the `SocketEngineSpec.polling` property. 39 | - Enable bitcode for most platforms. 40 | - Fix [#882](https://github.com/socketio/socket.io-client-swift/issues/882). This adds a new method 41 | `SocketManger.removeSocket(_:)` that should be called if when you no longer wish to use a socket again. 42 | This will cause the engine to no longer keep a strong reference to the socket and no longer track it. 43 | 44 | # v13.0.1 45 | 46 | - Fix not setting handleQueue on `SocketManager` 47 | 48 | # v13.0.0 49 | 50 | Checkout out the migration guide in Usage Docs for a more detailed guide on how to migrate to this version. 51 | 52 | What's new: 53 | --- 54 | 55 | - Adds a new `SocketManager` class that multiplexes multiple namespaces through a single engine. 56 | - Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs. 57 | - watchOS support. 58 | 59 | Important API changes 60 | --- 61 | 62 | - Many properties that were previously on `SocketIOClient` have been moved to the `SocketManager`. 63 | - `SocketIOClientOption.nsp` has been removed. Use `SocketManager.socket(forNamespace:)` to create/get a socket attached to a specific namespace. 64 | - Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs. 65 | - Makes the framework a single target. 66 | - Updates Starscream to 3.0 67 | 68 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "daltoniam/Starscream" ~> 3.0 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "daltoniam/Starscream" "3.0.5" 2 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SSCommonCrypto", 6 | "repositoryURL": "https://github.com/daltoniam/common-crypto-spm", 7 | "state": { 8 | "branch": null, 9 | "revision": "2eb3aff0fb57f92f5722fac5d6d20bf64669ca66", 10 | "version": "1.1.0" 11 | } 12 | }, 13 | { 14 | "package": "Starscream", 15 | "repositoryURL": "https://github.com/daltoniam/Starscream", 16 | "state": { 17 | "branch": null, 18 | "revision": "114e5df9b6251970a069e8f1c0cbb5802759f0a9", 19 | "version": "3.0.5" 20 | } 21 | }, 22 | { 23 | "package": "SSCZLib", 24 | "repositoryURL": "https://github.com/daltoniam/zlib-spm.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "83ac8d719a2f3aa775dbdf116a57f56fb2c49abb", 28 | "version": "1.1.0" 29 | } 30 | } 31 | ] 32 | }, 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SocketIO", 7 | products: [ 8 | .library(name: "SocketIO", targets: ["SocketIO"]) 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/daltoniam/Starscream", .upToNextMinor(from: "3.0.0")), 12 | ], 13 | targets: [ 14 | .target(name: "SocketIO", dependencies: ["Starscream"]), 15 | .testTarget(name: "TestSocketIO", dependencies: ["SocketIO"]), 16 | ] 17 | ) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/socketio/socket.io-client-swift.svg?branch=master)](https://travis-ci.org/socketio/socket.io-client-swift) 2 | 3 | # Socket.IO-Client-Swift 4 | Socket.IO-client for iOS/OS X. 5 | 6 | ## Example 7 | ```swift 8 | import SocketIO 9 | 10 | let manager = SocketManager(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .compress]) 11 | let socket = manager.defaultSocket 12 | 13 | socket.on(clientEvent: .connect) {data, ack in 14 | print("socket connected") 15 | } 16 | 17 | socket.on("currentAmount") {data, ack in 18 | guard let cur = data[0] as? Double else { return } 19 | 20 | socket.emitWithAck("canUpdate", cur).timingOut(after: 0) {data in 21 | socket.emit("update", ["amount": cur + 2.50]) 22 | } 23 | 24 | ack.with("Got your currentAmount", "dude") 25 | } 26 | 27 | socket.connect() 28 | ``` 29 | 30 | ## Objective-C Example 31 | ```objective-c 32 | @import SocketIO; 33 | 34 | NSURL* url = [[NSURL alloc] initWithString:@"http://localhost:8080"]; 35 | SocketManager* manager = [[SocketManager alloc] initWithSocketURL:url config:@{@"log": @YES, @"compress": @YES}]; 36 | SocketIOClient* socket = manager.defaultSocket; 37 | 38 | [socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) { 39 | NSLog(@"socket connected"); 40 | }]; 41 | 42 | [socket on:@"currentAmount" callback:^(NSArray* data, SocketAckEmitter* ack) { 43 | double cur = [[data objectAtIndex:0] floatValue]; 44 | 45 | [[socket emitWithAck:@"canUpdate" with:@[@(cur)]] timingOutAfter:0 callback:^(NSArray* data) { 46 | [socket emit:@"update" with:@[@{@"amount": @(cur + 2.50)}]]; 47 | }]; 48 | 49 | [ack with:@[@"Got your currentAmount, ", @"dude"]]; 50 | }]; 51 | 52 | [socket connect]; 53 | 54 | ``` 55 | 56 | ## Features 57 | - Supports socket.io 2.0+ (For socket.io 1.0 use v9.x) 58 | - Supports binary 59 | - Supports Polling and WebSockets 60 | - Supports TLS/SSL 61 | - Can be used from Objective-C 62 | 63 | ## FAQS 64 | Checkout the [FAQs](https://nuclearace.github.io/Socket.IO-Client-Swift/faq.html) for commonly asked questions. 65 | 66 | Checkout the [12to13](https://nuclearace.github.io/Socket.IO-Client-Swift/12to13.html) guide for migrating to v13. 67 | 68 | 69 | ## Installation 70 | Requires Swift 4/Xcode 9.x 71 | 72 | If you need Swift 2.3 use the [swift2.3 tag](https://github.com/socketio/socket.io-client-swift/releases/tag/swift2.3) (Pre-Swift 4 support is no longer maintained) 73 | 74 | If you need Swift 3.x use v11.1.3. 75 | 76 | ### Swift Package Manager 77 | Add the project as a dependency to your Package.swift: 78 | ```swift 79 | // swift-tools-version:4.0 80 | 81 | import PackageDescription 82 | 83 | let package = Package( 84 | name: "socket.io-test", 85 | products: [ 86 | .executable(name: "socket.io-test", targets: ["YourTargetName"]) 87 | ], 88 | dependencies: [ 89 | .package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMinor(from: "13.3.0")) 90 | ], 91 | targets: [ 92 | .target(name: "YourTargetName", dependencies: ["SocketIO"], path: "./Path/To/Your/Sources") 93 | ] 94 | ) 95 | ``` 96 | 97 | Then import `import SocketIO`. 98 | 99 | ### Carthage 100 | Add this line to your `Cartfile`: 101 | ``` 102 | github "socketio/socket.io-client-swift" ~> 13.3.0 103 | ``` 104 | 105 | Run `carthage update --platform ios,macosx`. 106 | 107 | Add the `Starscream` and `SocketIO` frameworks to your projects and follow the usual Carthage process. 108 | 109 | ### CocoaPods 1.0.0 or later 110 | Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`: 111 | 112 | ```ruby 113 | use_frameworks! 114 | 115 | target 'YourApp' do 116 | pod 'Socket.IO-Client-Swift', '~> 13.3.0' 117 | end 118 | ``` 119 | 120 | Install pods: 121 | 122 | ``` 123 | $ pod install 124 | ``` 125 | 126 | Import the module: 127 | 128 | Swift: 129 | ```swift 130 | import SocketIO 131 | ``` 132 | 133 | Objective-C: 134 | 135 | ```Objective-C 136 | @import SocketIO; 137 | ``` 138 | 139 | 140 | # [Docs](https://nuclearace.github.io/Socket.IO-Client-Swift/index.html) 141 | 142 | - [Client](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketIOClient.html) 143 | - [Manager](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketManager.html) 144 | - [Engine](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketEngine.html) 145 | - [Options](https://nuclearace.github.io/Socket.IO-Client-Swift/Enums/SocketIOClientOption.html) 146 | 147 | ## Detailed Example 148 | A more detailed example can be found [here](https://github.com/nuclearace/socket.io-client-swift-example) 149 | 150 | An example using the Swift Package Manager can be found [here](https://github.com/nuclearace/socket.io-client-swift-spm-example) 151 | 152 | ## License 153 | MIT 154 | -------------------------------------------------------------------------------- /Socket.IO-Client-Swift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Socket.IO-Client-Swift" 3 | s.module_name = "SocketIO" 4 | s.version = "13.3.1" 5 | s.summary = "Socket.IO-client for iOS and OS X" 6 | s.description = <<-DESC 7 | Socket.IO-client for iOS and OS X. 8 | Supports ws/wss/polling connections and binary. 9 | For socket.io 2.0+ and Swift. 10 | DESC 11 | s.homepage = "https://github.com/socketio/socket.io-client-swift" 12 | s.license = { :type => 'MIT' } 13 | s.author = { "Erik" => "nuclear.ace@gmail.com" } 14 | s.ios.deployment_target = '8.0' 15 | s.osx.deployment_target = '10.10' 16 | s.tvos.deployment_target = '9.0' 17 | s.watchos.deployment_target = '2.0' 18 | s.requires_arc = true 19 | s.source = { 20 | :git => "https://github.com/socketio/socket.io-client-swift.git", 21 | :tag => 'v13.3.1', 22 | :submodules => true 23 | } 24 | s.pod_target_xcconfig = { 25 | 'SWIFT_VERSION' => '4.0' 26 | } 27 | s.source_files = "Source/SocketIO/**/*.swift", "Source/SocketIO/*.swift" 28 | s.dependency "Starscream", "~> 3.0.2" 29 | end 30 | -------------------------------------------------------------------------------- /Socket.IO-Client-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Socket.IO-Client-Swift.xcodeproj/xcshareddata/xcschemes/SocketIO.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /SocketIO/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SocketIO/SocketIO.h: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIO-Mac.h 3 | // SocketIO-Mac 4 | // 5 | // Created by Nacho Soto on 7/11/15. 6 | // 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SocketIO-Mac. 12 | FOUNDATION_EXPORT double SocketIO_MacVersionNumber; 13 | 14 | //! Project version string for SocketIO-Mac. 15 | FOUNDATION_EXPORT const unsigned char SocketIO_MacVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/SocketIO/Ack/SocketAckEmitter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckEmitter.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 9/16/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | /// A class that represents a waiting ack call. 29 | /// 30 | /// **NOTE**: You should not store this beyond the life of the event handler. 31 | public final class SocketAckEmitter : NSObject { 32 | private unowned let socket: SocketIOClient 33 | private let ackNum: Int 34 | 35 | /// A view into this emitter where emits do not check for binary data. 36 | /// 37 | /// Usage: 38 | /// 39 | /// ```swift 40 | /// ack.rawEmitView.with(myObject) 41 | /// ``` 42 | /// 43 | /// **NOTE**: It is not safe to hold on to this view beyond the life of the socket. 44 | @objc 45 | public private(set) lazy var rawEmitView = SocketRawAckView(socket: socket, ackNum: ackNum) 46 | 47 | // MARK: Properties 48 | 49 | /// If true, this handler is expecting to be acked. Call `with(_: SocketData...)` to ack. 50 | public var expected: Bool { 51 | return ackNum != -1 52 | } 53 | 54 | // MARK: Initializers 55 | 56 | /// Creates a new `SocketAckEmitter`. 57 | /// 58 | /// - parameter socket: The socket for this emitter. 59 | /// - parameter ackNum: The ack number for this emitter. 60 | public init(socket: SocketIOClient, ackNum: Int) { 61 | self.socket = socket 62 | self.ackNum = ackNum 63 | } 64 | 65 | // MARK: Methods 66 | 67 | /// Call to ack receiving this event. 68 | /// 69 | /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` 70 | /// will be emitted. The structure of the error data is `[ackNum, items, theError]` 71 | /// 72 | /// - parameter items: A variable number of items to send when acking. 73 | public func with(_ items: SocketData...) { 74 | guard ackNum != -1 else { return } 75 | 76 | do { 77 | socket.emitAck(ackNum, with: try items.map({ try $0.socketRepresentation() })) 78 | } catch { 79 | socket.handleClientEvent(.error, data: [ackNum, items, error]) 80 | } 81 | } 82 | 83 | /// Call to ack receiving this event. 84 | /// 85 | /// - parameter items: An array of items to send when acking. Use `[]` to send nothing. 86 | @objc 87 | public func with(_ items: [Any]) { 88 | guard ackNum != -1 else { return } 89 | 90 | socket.emitAck(ackNum, with: items) 91 | } 92 | 93 | } 94 | 95 | /// A class that represents an emit that will request an ack that has not yet been sent. 96 | /// Call `timingOut(after:callback:)` to complete the emit 97 | /// Example: 98 | /// 99 | /// ```swift 100 | /// socket.emitWithAck("myEvent").timingOut(after: 1) {data in 101 | /// ... 102 | /// } 103 | /// ``` 104 | public final class OnAckCallback : NSObject { 105 | private let ackNumber: Int 106 | private let binary: Bool 107 | private let items: [Any] 108 | 109 | private weak var socket: SocketIOClient? 110 | 111 | init(ackNumber: Int, items: [Any], socket: SocketIOClient, binary: Bool = true) { 112 | self.ackNumber = ackNumber 113 | self.items = items 114 | self.socket = socket 115 | self.binary = binary 116 | } 117 | 118 | deinit { 119 | DefaultSocketLogger.Logger.log("OnAckCallback for \(ackNumber) being released", type: "OnAckCallback") 120 | } 121 | 122 | // MARK: Methods 123 | 124 | /// Completes an emitWithAck. If this isn't called, the emit never happens. 125 | /// 126 | /// - parameter seconds: The number of seconds before this emit times out if an ack hasn't been received. 127 | /// - parameter callback: The callback called when an ack is received, or when a timeout happens. 128 | /// To check for timeout, use `SocketAckStatus`'s `noAck` case. 129 | @objc 130 | public func timingOut(after seconds: Double, callback: @escaping AckCallback) { 131 | guard let socket = self.socket, ackNumber != -1 else { return } 132 | 133 | socket.ackHandlers.addAck(ackNumber, callback: callback) 134 | socket.emit(items, ack: ackNumber, binary: binary) 135 | 136 | guard seconds != 0 else { return } 137 | 138 | socket.manager?.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {[weak socket] in 139 | guard let socket = socket else { return } 140 | 141 | socket.ackHandlers.timeoutAck(self.ackNumber) 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Source/SocketIO/Ack/SocketAckManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckManager.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/3/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | /// The status of an ack. 29 | public enum SocketAckStatus : String { 30 | // MARK: Cases 31 | 32 | /// The ack timed out. 33 | case noAck = "NO ACK" 34 | } 35 | 36 | private struct SocketAck : Hashable { 37 | let ack: Int 38 | var callback: AckCallback! 39 | var hashValue: Int { 40 | return ack.hashValue 41 | } 42 | 43 | init(ack: Int) { 44 | self.ack = ack 45 | } 46 | 47 | init(ack: Int, callback: @escaping AckCallback) { 48 | self.ack = ack 49 | self.callback = callback 50 | } 51 | 52 | fileprivate static func <(lhs: SocketAck, rhs: SocketAck) -> Bool { 53 | return lhs.ack < rhs.ack 54 | } 55 | 56 | fileprivate static func ==(lhs: SocketAck, rhs: SocketAck) -> Bool { 57 | return lhs.ack == rhs.ack 58 | } 59 | } 60 | 61 | class SocketAckManager { 62 | private var acks = Set(minimumCapacity: 1) 63 | 64 | func addAck(_ ack: Int, callback: @escaping AckCallback) { 65 | acks.insert(SocketAck(ack: ack, callback: callback)) 66 | } 67 | 68 | /// Should be called on handle queue 69 | func executeAck(_ ack: Int, with items: [Any]) { 70 | acks.remove(SocketAck(ack: ack))?.callback(items) 71 | } 72 | 73 | /// Should be called on handle queue 74 | func timeoutAck(_ ack: Int) { 75 | acks.remove(SocketAck(ack: ack))?.callback?([SocketAckStatus.noAck.rawValue]) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Source/SocketIO/Client/SocketAnyEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAnyEvent.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/28/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Represents some event that was received. 28 | public final class SocketAnyEvent : NSObject { 29 | // MARK: Properties 30 | 31 | /// The event name. 32 | @objc 33 | public let event: String 34 | 35 | /// The data items for this event. 36 | @objc 37 | public let items: [Any]? 38 | 39 | /// The description of this event. 40 | override public var description: String { 41 | return "SocketAnyEvent: Event: \(event) items: \(String(describing: items))" 42 | } 43 | 44 | init(event: String, items: [Any]?) { 45 | self.event = event 46 | self.items = items 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Source/SocketIO/Client/SocketEventHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventHandler.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// A wrapper around a handler. 28 | public struct SocketEventHandler { 29 | // MARK: Properties 30 | 31 | /// The event for this handler. 32 | public let event: String 33 | 34 | /// A unique identifier for this handler. 35 | public let id: UUID 36 | 37 | /// The actual handler function. 38 | public let callback: NormalCallback 39 | 40 | // MARK: Methods 41 | 42 | /// Causes this handler to be executed. 43 | /// 44 | /// - parameter with: The data that this handler should be called with. 45 | /// - parameter withAck: The ack number that this event expects. Pass -1 to say this event doesn't expect an ack. 46 | /// - parameter withSocket: The socket that is calling this event. 47 | public func executeCallback(with items: [Any], withAck ack: Int, withSocket socket: SocketIOClient) { 48 | callback(items, SocketAckEmitter(socket: socket, ackNum: ack)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/SocketIO/Client/SocketIOClientConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOClientConfiguration.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 8/13/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | /// An array-like type that holds `SocketIOClientOption`s 26 | public struct SocketIOClientConfiguration : ExpressibleByArrayLiteral, Collection, MutableCollection { 27 | // MARK: Typealiases 28 | 29 | /// Type of element stored. 30 | public typealias Element = SocketIOClientOption 31 | 32 | /// Index type. 33 | public typealias Index = Array.Index 34 | 35 | /// Iterator type. 36 | public typealias Iterator = Array.Iterator 37 | 38 | /// SubSequence type. 39 | public typealias SubSequence = Array.SubSequence 40 | 41 | // MARK: Properties 42 | 43 | private var backingArray = [SocketIOClientOption]() 44 | 45 | /// The start index of this collection. 46 | public var startIndex: Index { 47 | return backingArray.startIndex 48 | } 49 | 50 | /// The end index of this collection. 51 | public var endIndex: Index { 52 | return backingArray.endIndex 53 | } 54 | 55 | /// Whether this collection is empty. 56 | public var isEmpty: Bool { 57 | return backingArray.isEmpty 58 | } 59 | 60 | /// The number of elements stored in this collection. 61 | public var count: Index.Stride { 62 | return backingArray.count 63 | } 64 | 65 | /// The first element in this collection. 66 | public var first: Element? { 67 | return backingArray.first 68 | } 69 | 70 | public subscript(position: Index) -> Element { 71 | get { 72 | return backingArray[position] 73 | } 74 | 75 | set { 76 | backingArray[position] = newValue 77 | } 78 | } 79 | 80 | public subscript(bounds: Range) -> SubSequence { 81 | get { 82 | return backingArray[bounds] 83 | } 84 | 85 | set { 86 | backingArray[bounds] = newValue 87 | } 88 | } 89 | 90 | // MARK: Initializers 91 | 92 | /// Creates a new `SocketIOClientConfiguration` from an array literal. 93 | /// 94 | /// - parameter arrayLiteral: The elements. 95 | public init(arrayLiteral elements: Element...) { 96 | backingArray = elements 97 | } 98 | 99 | // MARK: Methods 100 | 101 | /// Creates an iterator for this collection. 102 | /// 103 | /// - returns: An iterator over this collection. 104 | public func makeIterator() -> Iterator { 105 | return backingArray.makeIterator() 106 | } 107 | 108 | /// - returns: The index after index. 109 | public func index(after i: Index) -> Index { 110 | return backingArray.index(after: i) 111 | } 112 | 113 | /// Special method that inserts `element` into the collection, replacing any other instances of `element`. 114 | /// 115 | /// - parameter element: The element to insert. 116 | /// - parameter replacing: Whether to replace any occurrences of element to the new item. Default is `true`. 117 | public mutating func insert(_ element: Element, replacing replace: Bool = true) { 118 | for i in 0.. Any 30 | } 31 | 32 | /// The options for a client. 33 | public enum SocketIOClientOption : ClientOption { 34 | /// If given, the WebSocket transport will attempt to use compression. 35 | case compress 36 | 37 | /// A dictionary of GET parameters that will be included in the connect url. 38 | case connectParams([String: Any]) 39 | 40 | /// An array of cookies that will be sent during the initial connection. 41 | case cookies([HTTPCookie]) 42 | 43 | /// Any extra HTTP headers that should be sent during the initial connection. 44 | case extraHeaders([String: String]) 45 | 46 | /// If passed `true`, will cause the client to always create a new engine. Useful for debugging, 47 | /// or when you want to be sure no state from previous engines is being carried over. 48 | case forceNew(Bool) 49 | 50 | /// If passed `true`, the only transport that will be used will be HTTP long-polling. 51 | case forcePolling(Bool) 52 | 53 | /// If passed `true`, the only transport that will be used will be WebSockets. 54 | case forceWebsockets(Bool) 55 | 56 | /// The queue that all interaction with the client should occur on. This is the queue that event handlers are 57 | /// called on. 58 | /// 59 | /// **This should be a serial queue! Concurrent queues are not supported and might cause crashes and races**. 60 | case handleQueue(DispatchQueue) 61 | 62 | /// If passed `true`, the client will log debug information. This should be turned off in production code. 63 | case log(Bool) 64 | 65 | /// Used to pass in a custom logger. 66 | case logger(SocketLogger) 67 | 68 | /// A custom path to socket.io. Only use this if the socket.io server is configured to look for this path. 69 | case path(String) 70 | 71 | /// If passed `false`, the client will not reconnect when it loses connection. Useful if you want full control 72 | /// over when reconnects happen. 73 | case reconnects(Bool) 74 | 75 | /// The number of times to try and reconnect before giving up. Pass `-1` to [never give up](https://www.youtube.com/watch?v=dQw4w9WgXcQ). 76 | case reconnectAttempts(Int) 77 | 78 | /// The number of seconds to wait before reconnect attempts. 79 | case reconnectWait(Int) 80 | 81 | /// Set `true` if your server is using secure transports. 82 | case secure(Bool) 83 | 84 | /// Allows you to set which certs are valid. Useful for SSL pinning. 85 | case security(SSLSecurity) 86 | 87 | /// If you're using a self-signed set. Only use for development. 88 | case selfSigned(Bool) 89 | 90 | /// Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs. 91 | case sessionDelegate(URLSessionDelegate) 92 | 93 | // MARK: Properties 94 | 95 | /// The description of this option. 96 | public var description: String { 97 | let description: String 98 | 99 | switch self { 100 | case .compress: 101 | description = "compress" 102 | case .connectParams: 103 | description = "connectParams" 104 | case .cookies: 105 | description = "cookies" 106 | case .extraHeaders: 107 | description = "extraHeaders" 108 | case .forceNew: 109 | description = "forceNew" 110 | case .forcePolling: 111 | description = "forcePolling" 112 | case .forceWebsockets: 113 | description = "forceWebsockets" 114 | case .handleQueue: 115 | description = "handleQueue" 116 | case .log: 117 | description = "log" 118 | case .logger: 119 | description = "logger" 120 | case .path: 121 | description = "path" 122 | case .reconnects: 123 | description = "reconnects" 124 | case .reconnectAttempts: 125 | description = "reconnectAttempts" 126 | case .reconnectWait: 127 | description = "reconnectWait" 128 | case .secure: 129 | description = "secure" 130 | case .selfSigned: 131 | description = "selfSigned" 132 | case .security: 133 | description = "security" 134 | case .sessionDelegate: 135 | description = "sessionDelegate" 136 | } 137 | 138 | return description 139 | } 140 | 141 | func getSocketIOOptionValue() -> Any { 142 | let value: Any 143 | 144 | switch self { 145 | case .compress: 146 | value = true 147 | case let .connectParams(params): 148 | value = params 149 | case let .cookies(cookies): 150 | value = cookies 151 | case let .extraHeaders(headers): 152 | value = headers 153 | case let .forceNew(force): 154 | value = force 155 | case let .forcePolling(force): 156 | value = force 157 | case let .forceWebsockets(force): 158 | value = force 159 | case let .handleQueue(queue): 160 | value = queue 161 | case let .log(log): 162 | value = log 163 | case let .logger(logger): 164 | value = logger 165 | case let .path(path): 166 | value = path 167 | case let .reconnects(reconnects): 168 | value = reconnects 169 | case let .reconnectAttempts(attempts): 170 | value = attempts 171 | case let .reconnectWait(wait): 172 | value = wait 173 | case let .secure(secure): 174 | value = secure 175 | case let .security(security): 176 | value = security 177 | case let .selfSigned(signed): 178 | value = signed 179 | case let .sessionDelegate(delegate): 180 | value = delegate 181 | } 182 | 183 | return value 184 | } 185 | 186 | // MARK: Operators 187 | 188 | /// Compares whether two options are the same. 189 | /// 190 | /// - parameter lhs: Left operand to compare. 191 | /// - parameter rhs: Right operand to compare. 192 | /// - returns: `true` if the two are the same option. 193 | public static func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool { 194 | return lhs.description == rhs.description 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /Source/SocketIO/Client/SocketIOStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketIOStatus.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 8/14/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Represents state of a manager or client. 28 | @objc 29 | public enum SocketIOStatus : Int, CustomStringConvertible { 30 | // MARK: Cases 31 | 32 | /// The client/manager has never been connected. Or the client has been reset. 33 | case notConnected 34 | 35 | /// The client/manager was once connected, but not anymore. 36 | case disconnected 37 | 38 | /// The client/manager is in the process of connecting. 39 | case connecting 40 | 41 | /// The client/manager is currently connected. 42 | case connected 43 | 44 | // MARK: Properties 45 | 46 | /// - returns: True if this client/manager is connected/connecting to a server. 47 | public var active: Bool { 48 | return self == .connected || self == .connecting 49 | } 50 | 51 | public var description: String { 52 | switch self { 53 | case .connected: return "connected" 54 | case .connecting: return "connecting" 55 | case .disconnected: return "disconnected" 56 | case .notConnected: return "notConnected" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Source/SocketIO/Client/SocketRawView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketRawView.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/30/18. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Class that gives a backwards compatible way to cause an emit not to recursively check for Data objects. 28 | /// 29 | /// Usage: 30 | /// 31 | /// ```swift 32 | /// socket.rawEmitView.emit("myEvent", myObject) 33 | /// ``` 34 | public final class SocketRawView : NSObject { 35 | private unowned let socket: SocketIOClient 36 | 37 | init(socket: SocketIOClient) { 38 | self.socket = socket 39 | } 40 | 41 | /// Send an event to the server, with optional data items. 42 | /// 43 | /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` 44 | /// will be emitted. The structure of the error data is `[eventName, items, theError]` 45 | /// 46 | /// - parameter event: The event to send. 47 | /// - parameter items: The items to send with this event. May be left out. 48 | public func emit(_ event: String, _ items: SocketData...) { 49 | do { 50 | try emit(event, with: items.map({ try $0.socketRepresentation() })) 51 | } catch { 52 | DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)", 53 | type: "SocketIOClient") 54 | 55 | socket.handleClientEvent(.error, data: [event, items, error]) 56 | } 57 | } 58 | 59 | /// Same as emit, but meant for Objective-C 60 | /// 61 | /// - parameter event: The event to send. 62 | /// - parameter items: The items to send with this event. Send an empty array to send no data. 63 | @objc 64 | public func emit(_ event: String, with items: [Any]) { 65 | socket.emit([event] + items, binary: false) 66 | } 67 | 68 | /// Sends a message to the server, requesting an ack. 69 | /// 70 | /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack. 71 | /// Check that your server's api will ack the event being sent. 72 | /// 73 | /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` 74 | /// will be emitted. The structure of the error data is `[eventName, items, theError]` 75 | /// 76 | /// Example: 77 | /// 78 | /// ```swift 79 | /// socket.emitWithAck("myEvent", 1).timingOut(after: 1) {data in 80 | /// ... 81 | /// } 82 | /// ``` 83 | /// 84 | /// - parameter event: The event to send. 85 | /// - parameter items: The items to send with this event. May be left out. 86 | /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent. 87 | public func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback { 88 | do { 89 | return emitWithAck(event, with: try items.map({ try $0.socketRepresentation() })) 90 | } catch { 91 | DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)", 92 | type: "SocketIOClient") 93 | 94 | socket.handleClientEvent(.error, data: [event, items, error]) 95 | 96 | return OnAckCallback(ackNumber: -1, items: [], socket: socket) 97 | } 98 | } 99 | 100 | /// Same as emitWithAck, but for Objective-C 101 | /// 102 | /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack. 103 | /// Check that your server's api will ack the event being sent. 104 | /// 105 | /// Example: 106 | /// 107 | /// ```swift 108 | /// socket.emitWithAck("myEvent", with: [1]).timingOut(after: 1) {data in 109 | /// ... 110 | /// } 111 | /// ``` 112 | /// 113 | /// - parameter event: The event to send. 114 | /// - parameter items: The items to send with this event. Use `[]` to send nothing. 115 | /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent. 116 | @objc 117 | public func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback { 118 | return socket.createOnAck([event] + items, binary: false) 119 | } 120 | } 121 | 122 | /// Class that gives a backwards compatible way to cause an emit not to recursively check for Data objects. 123 | /// 124 | /// Usage: 125 | /// 126 | /// ```swift 127 | /// ack.rawEmitView.with(myObject) 128 | /// ``` 129 | public final class SocketRawAckView : NSObject { 130 | private unowned let socket: SocketIOClient 131 | private let ackNum: Int 132 | 133 | init(socket: SocketIOClient, ackNum: Int) { 134 | self.socket = socket 135 | self.ackNum = ackNum 136 | } 137 | 138 | /// Call to ack receiving this event. 139 | /// 140 | /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error` 141 | /// will be emitted. The structure of the error data is `[ackNum, items, theError]` 142 | /// 143 | /// - parameter items: A variable number of items to send when acking. 144 | public func with(_ items: SocketData...) { 145 | guard ackNum != -1 else { return } 146 | 147 | do { 148 | socket.emit(try items.map({ try $0.socketRepresentation() }), ack: ackNum, binary: false, isAck: true) 149 | } catch { 150 | socket.handleClientEvent(.error, data: [ackNum, items, error]) 151 | } 152 | } 153 | 154 | /// Call to ack receiving this event. 155 | /// 156 | /// - parameter items: An array of items to send when acking. Use `[]` to send nothing. 157 | @objc 158 | public func with(_ items: [Any]) { 159 | guard ackNum != -1 else { return } 160 | 161 | socket.emit(items, ack: ackNum, binary: false, isAck: true) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Source/SocketIO/Engine/SocketEngineClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineClient.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/19/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | /// Declares that a type will be a delegate to an engine. 29 | @objc public protocol SocketEngineClient { 30 | // MARK: Methods 31 | 32 | /// Called when the engine errors. 33 | /// 34 | /// - parameter reason: The reason the engine errored. 35 | func engineDidError(reason: String) 36 | 37 | /// Called when the engine closes. 38 | /// 39 | /// - parameter reason: The reason that the engine closed. 40 | func engineDidClose(reason: String) 41 | 42 | /// Called when the engine opens. 43 | /// 44 | /// - parameter reason: The reason the engine opened. 45 | func engineDidOpen(reason: String) 46 | 47 | /// Called when the engine receives a pong message. 48 | func engineDidReceivePong() 49 | 50 | /// Called when the engine sends a ping to the server. 51 | func engineDidSendPing() 52 | 53 | /// Called when the engine has a message that must be parsed. 54 | /// 55 | /// - parameter msg: The message that needs parsing. 56 | func parseEngineMessage(_ msg: String) 57 | 58 | /// Called when the engine receives binary data. 59 | /// 60 | /// - parameter data: The data the engine received. 61 | func parseEngineBinaryData(_ data: Data) 62 | } 63 | -------------------------------------------------------------------------------- /Source/SocketIO/Engine/SocketEnginePacketType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEnginePacketType.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/7/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | /// Represents the type of engine.io packet types. 29 | @objc public enum SocketEnginePacketType : Int { 30 | /// Open message. 31 | case open 32 | 33 | /// Close message. 34 | case close 35 | 36 | /// Ping message. 37 | case ping 38 | 39 | /// Pong message. 40 | case pong 41 | 42 | /// Regular message. 43 | case message 44 | 45 | /// Upgrade message. 46 | case upgrade 47 | 48 | /// NOOP. 49 | case noop 50 | } 51 | -------------------------------------------------------------------------------- /Source/SocketIO/Engine/SocketEnginePollable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEnginePollable.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/15/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Protocol that is used to implement socket.io polling support 28 | public protocol SocketEnginePollable : SocketEngineSpec { 29 | // MARK: Properties 30 | 31 | /// `true` If engine's session has been invalidated. 32 | var invalidated: Bool { get } 33 | 34 | /// A queue of engine.io messages waiting for POSTing 35 | /// 36 | /// **You should not touch this directly** 37 | var postWait: [String] { get set } 38 | 39 | /// The URLSession that will be used for polling. 40 | var session: URLSession? { get } 41 | 42 | /// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to 43 | /// disconnect us. 44 | /// 45 | /// **Do not touch this directly** 46 | var waitingForPoll: Bool { get set } 47 | 48 | /// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to 49 | /// disconnect us. 50 | /// 51 | /// **Do not touch this directly** 52 | var waitingForPost: Bool { get set } 53 | 54 | // MARK: Methods 55 | 56 | /// Call to send a long-polling request. 57 | /// 58 | /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. 59 | func doPoll() 60 | 61 | /// Sends an engine.io message through the polling transport. 62 | /// 63 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. 64 | /// 65 | /// - parameter message: The message to send. 66 | /// - parameter withType: The type of message to send. 67 | /// - parameter withData: The data associated with this message. 68 | func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) 69 | 70 | /// Call to stop polling and invalidate the URLSession. 71 | func stopPolling() 72 | } 73 | 74 | // Default polling methods 75 | extension SocketEnginePollable { 76 | func createRequestForPostWithPostWait() -> URLRequest { 77 | defer { postWait.removeAll(keepingCapacity: true) } 78 | 79 | var postStr = "" 80 | 81 | for packet in postWait { 82 | postStr += "\(packet.utf16.count):\(packet)" 83 | } 84 | 85 | DefaultSocketLogger.Logger.log("Created POST string: \(postStr)", type: "SocketEnginePolling") 86 | 87 | var req = URLRequest(url: urlPollingWithSid) 88 | let postData = postStr.data(using: .utf8, allowLossyConversion: false)! 89 | 90 | addHeaders(to: &req) 91 | 92 | req.httpMethod = "POST" 93 | req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type") 94 | req.httpBody = postData 95 | req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length") 96 | 97 | return req 98 | } 99 | 100 | /// Call to send a long-polling request. 101 | /// 102 | /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request. 103 | public func doPoll() { 104 | guard polling && !waitingForPoll && connected && !closed else { return } 105 | 106 | var req = URLRequest(url: urlPollingWithSid) 107 | addHeaders(to: &req) 108 | 109 | doLongPoll(for: req) 110 | } 111 | 112 | func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> ()) { 113 | guard polling && !closed && !invalidated && !fastUpgrade else { return } 114 | 115 | DefaultSocketLogger.Logger.log("Doing polling \(req.httpMethod ?? "") \(req)", type: "SocketEnginePolling") 116 | 117 | session?.dataTask(with: req, completionHandler: callback).resume() 118 | } 119 | 120 | func doLongPoll(for req: URLRequest) { 121 | waitingForPoll = true 122 | 123 | doRequest(for: req) {[weak self] data, res, err in 124 | guard let this = self, this.polling else { return } 125 | guard let data = data, let res = res as? HTTPURLResponse, res.statusCode == 200 else { 126 | if let err = err { 127 | DefaultSocketLogger.Logger.error(err.localizedDescription, type: "SocketEnginePolling") 128 | } else { 129 | DefaultSocketLogger.Logger.error("Error during long poll request", type: "SocketEnginePolling") 130 | } 131 | 132 | if this.polling { 133 | this.didError(reason: err?.localizedDescription ?? "Error") 134 | } 135 | 136 | return 137 | } 138 | 139 | DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling") 140 | 141 | if let str = String(data: data, encoding: .utf8) { 142 | this.parsePollingMessage(str) 143 | } 144 | 145 | this.waitingForPoll = false 146 | 147 | if this.fastUpgrade { 148 | this.doFastUpgrade() 149 | } else if !this.closed && this.polling { 150 | this.doPoll() 151 | } 152 | } 153 | } 154 | 155 | private func flushWaitingForPost() { 156 | guard postWait.count != 0 && connected else { return } 157 | guard polling else { 158 | flushWaitingForPostToWebSocket() 159 | 160 | return 161 | } 162 | 163 | let req = createRequestForPostWithPostWait() 164 | 165 | waitingForPost = true 166 | 167 | DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling") 168 | 169 | doRequest(for: req) {[weak self] _, res, err in 170 | guard let this = self else { return } 171 | guard let res = res as? HTTPURLResponse, res.statusCode == 200 else { 172 | if let err = err { 173 | DefaultSocketLogger.Logger.error(err.localizedDescription, type: "SocketEnginePolling") 174 | } else { 175 | DefaultSocketLogger.Logger.error("Error flushing waiting posts", type: "SocketEnginePolling") 176 | } 177 | 178 | if this.polling { 179 | this.didError(reason: err?.localizedDescription ?? "Error") 180 | } 181 | 182 | return 183 | } 184 | 185 | this.waitingForPost = false 186 | 187 | if !this.fastUpgrade { 188 | this.flushWaitingForPost() 189 | this.doPoll() 190 | } 191 | } 192 | } 193 | 194 | func parsePollingMessage(_ str: String) { 195 | guard str.count != 1 else { return } 196 | 197 | DefaultSocketLogger.Logger.log("Got poll message: \(str)", type: "SocketEnginePolling") 198 | 199 | var reader = SocketStringReader(message: str) 200 | 201 | while reader.hasNext { 202 | if let n = Int(reader.readUntilOccurence(of: ":")) { 203 | parseEngineMessage(reader.read(count: n)) 204 | } else { 205 | parseEngineMessage(str) 206 | break 207 | } 208 | } 209 | } 210 | 211 | /// Sends an engine.io message through the polling transport. 212 | /// 213 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. 214 | /// 215 | /// - parameter message: The message to send. 216 | /// - parameter withType: The type of message to send. 217 | /// - parameter withData: The data associated with this message. 218 | public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) { 219 | DefaultSocketLogger.Logger.log("Sending poll: \(message) as type: \(type.rawValue)", type: "SocketEnginePolling") 220 | 221 | postWait.append(String(type.rawValue) + message) 222 | 223 | for data in datas { 224 | if case let .right(bin) = createBinaryDataForSend(using: data) { 225 | postWait.append(bin) 226 | } 227 | } 228 | 229 | if !waitingForPost { 230 | flushWaitingForPost() 231 | } 232 | } 233 | 234 | /// Call to stop polling and invalidate the URLSession. 235 | public func stopPolling() { 236 | waitingForPoll = false 237 | waitingForPost = false 238 | session?.finishTasksAndInvalidate() 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Source/SocketIO/Engine/SocketEngineSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineSpec.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/7/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import Starscream 28 | 29 | /// Specifies a SocketEngine. 30 | @objc public protocol SocketEngineSpec { 31 | // MARK: Properties 32 | 33 | /// The client for this engine. 34 | var client: SocketEngineClient? { get set } 35 | 36 | /// `true` if this engine is closed. 37 | var closed: Bool { get } 38 | 39 | /// If `true` the engine will attempt to use WebSocket compression. 40 | var compress: Bool { get } 41 | 42 | /// `true` if this engine is connected. Connected means that the initial poll connect has succeeded. 43 | var connected: Bool { get } 44 | 45 | /// The connect parameters sent during a connect. 46 | var connectParams: [String: Any]? { get set } 47 | 48 | /// An array of HTTPCookies that are sent during the connection. 49 | var cookies: [HTTPCookie]? { get } 50 | 51 | /// The queue that all engine actions take place on. 52 | var engineQueue: DispatchQueue { get } 53 | 54 | /// A dictionary of extra http headers that will be set during connection. 55 | var extraHeaders: [String: String]? { get set } 56 | 57 | /// When `true`, the engine is in the process of switching to WebSockets. 58 | var fastUpgrade: Bool { get } 59 | 60 | /// When `true`, the engine will only use HTTP long-polling as a transport. 61 | var forcePolling: Bool { get } 62 | 63 | /// When `true`, the engine will only use WebSockets as a transport. 64 | var forceWebsockets: Bool { get } 65 | 66 | /// If `true`, the engine is currently in HTTP long-polling mode. 67 | var polling: Bool { get } 68 | 69 | /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets. 70 | var probing: Bool { get } 71 | 72 | /// The session id for this engine. 73 | var sid: String { get } 74 | 75 | /// The path to engine.io. 76 | var socketPath: String { get } 77 | 78 | /// The url for polling. 79 | var urlPolling: URL { get } 80 | 81 | /// The url for WebSockets. 82 | var urlWebSocket: URL { get } 83 | 84 | /// If `true`, then the engine is currently in WebSockets mode. 85 | @available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets") 86 | var websocket: Bool { get } 87 | 88 | /// The WebSocket for this engine. 89 | var ws: WebSocket? { get } 90 | 91 | // MARK: Initializers 92 | 93 | /// Creates a new engine. 94 | /// 95 | /// - parameter client: The client for this engine. 96 | /// - parameter url: The url for this engine. 97 | /// - parameter options: The options for this engine. 98 | init(client: SocketEngineClient, url: URL, options: [String: Any]?) 99 | 100 | // MARK: Methods 101 | 102 | /// Starts the connection to the server. 103 | func connect() 104 | 105 | /// Called when an error happens during execution. Causes a disconnection. 106 | func didError(reason: String) 107 | 108 | /// Disconnects from the server. 109 | /// 110 | /// - parameter reason: The reason for the disconnection. This is communicated up to the client. 111 | func disconnect(reason: String) 112 | 113 | /// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in 114 | /// WebSocket mode. 115 | /// 116 | /// **You shouldn't call this directly** 117 | func doFastUpgrade() 118 | 119 | /// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when 120 | /// the engine is attempting to upgrade to WebSocket it does not do any POSTing. 121 | /// 122 | /// **You shouldn't call this directly** 123 | func flushWaitingForPostToWebSocket() 124 | 125 | /// Parses raw binary received from engine.io. 126 | /// 127 | /// - parameter data: The data to parse. 128 | func parseEngineData(_ data: Data) 129 | 130 | /// Parses a raw engine.io packet. 131 | /// 132 | /// - parameter message: The message to parse. 133 | func parseEngineMessage(_ message: String) 134 | 135 | /// Writes a message to engine.io, independent of transport. 136 | /// 137 | /// - parameter msg: The message to send. 138 | /// - parameter type: The type of this message. 139 | /// - parameter data: Any data that this message has. 140 | func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) 141 | } 142 | 143 | extension SocketEngineSpec { 144 | var urlPollingWithSid: URL { 145 | var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)! 146 | com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)" 147 | 148 | return com.url! 149 | } 150 | 151 | var urlWebSocketWithSid: URL { 152 | var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)! 153 | com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)") 154 | 155 | return com.url! 156 | } 157 | 158 | func addHeaders(to req: inout URLRequest, includingCookies additionalCookies: [HTTPCookie]? = nil) { 159 | var cookiesToAdd: [HTTPCookie] = cookies ?? [] 160 | cookiesToAdd += additionalCookies ?? [] 161 | 162 | if !cookiesToAdd.isEmpty { 163 | req.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookiesToAdd) 164 | } 165 | 166 | if let extraHeaders = extraHeaders { 167 | for (headerName, value) in extraHeaders { 168 | req.setValue(value, forHTTPHeaderField: headerName) 169 | } 170 | } 171 | } 172 | 173 | func createBinaryDataForSend(using data: Data) -> Either { 174 | if polling { 175 | return .right("b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))) 176 | } else { 177 | return .left(Data(bytes: [0x4]) + data) 178 | } 179 | } 180 | 181 | /// Send an engine message (4) 182 | func send(_ msg: String, withData datas: [Data]) { 183 | write(msg, withType: .message, withData: datas) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /Source/SocketIO/Engine/SocketEngineWebsocket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineWebsocket.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/15/16. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | import Starscream 28 | 29 | /// Protocol that is used to implement socket.io WebSocket support 30 | public protocol SocketEngineWebsocket : SocketEngineSpec { 31 | // MARK: Methods 32 | 33 | /// Sends an engine.io message through the WebSocket transport. 34 | /// 35 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. 36 | /// 37 | /// - parameter message: The message to send. 38 | /// - parameter withType: The type of message to send. 39 | /// - parameter withData: The data associated with this message. 40 | func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) 41 | } 42 | 43 | // WebSocket methods 44 | extension SocketEngineWebsocket { 45 | func probeWebSocket() { 46 | if ws?.isConnected ?? false { 47 | sendWebSocketMessage("probe", withType: .ping, withData: []) 48 | } 49 | } 50 | 51 | /// Sends an engine.io message through the WebSocket transport. 52 | /// 53 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. 54 | /// 55 | /// - parameter message: The message to send. 56 | /// - parameter withType: The type of message to send. 57 | /// - parameter withData: The data associated with this message. 58 | public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) { 59 | DefaultSocketLogger.Logger.log("Sending ws: \(str) as type: \(type.rawValue)", type: "SocketEngineWebSocket") 60 | 61 | ws?.write(string: "\(type.rawValue)\(str)") 62 | 63 | for data in datas { 64 | if case let .left(bin) = createBinaryDataForSend(using: data) { 65 | ws?.write(data: bin) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Source/SocketIO/Manager/SocketManagerSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Erik Little on 10/18/17. 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | import Dispatch 23 | import Foundation 24 | 25 | // TODO Fix the types so that we aren't using concrete types 26 | 27 | /// 28 | /// A manager for a socket.io connection. 29 | /// 30 | /// A `SocketManagerSpec` is responsible for multiplexing multiple namespaces through a single `SocketEngineSpec`. 31 | /// 32 | /// Example with `SocketManager`: 33 | /// 34 | /// ```swift 35 | /// let manager = SocketManager(socketURL: URL(string:"http://localhost:8080/")!) 36 | /// let defaultNamespaceSocket = manager.defaultSocket 37 | /// let swiftSocket = manager.socket(forNamespace: "/swift") 38 | /// 39 | /// // defaultNamespaceSocket and swiftSocket both share a single connection to the server 40 | /// ``` 41 | /// 42 | /// Sockets created through the manager are retained by the manager. So at the very least, a single strong reference 43 | /// to the manager must be maintained to keep sockets alive. 44 | /// 45 | /// To disconnect a socket and remove it from the manager, either call `SocketIOClient.disconnect()` on the socket, 46 | /// or call one of the `disconnectSocket` methods on this class. 47 | /// 48 | @objc 49 | public protocol SocketManagerSpec : AnyObject, SocketEngineClient { 50 | // MARK: Properties 51 | 52 | /// Returns the socket associated with the default namespace ("/"). 53 | var defaultSocket: SocketIOClient { get } 54 | 55 | /// The engine for this manager. 56 | var engine: SocketEngineSpec? { get set } 57 | 58 | /// If `true` then every time `connect` is called, a new engine will be created. 59 | var forceNew: Bool { get set } 60 | 61 | // TODO Per socket queues? 62 | /// The queue that all interaction with the client should occur on. This is the queue that event handlers are 63 | /// called on. 64 | var handleQueue: DispatchQueue { get set } 65 | 66 | /// The sockets in this manager indexed by namespace. 67 | var nsps: [String: SocketIOClient] { get set } 68 | 69 | /// If `true`, this manager will try and reconnect on any disconnects. 70 | var reconnects: Bool { get set } 71 | 72 | /// The number of seconds to wait before attempting to reconnect. 73 | var reconnectWait: Int { get set } 74 | 75 | /// The URL of the socket.io server. 76 | var socketURL: URL { get } 77 | 78 | /// The status of this manager. 79 | var status: SocketIOStatus { get } 80 | 81 | // MARK: Methods 82 | 83 | /// Connects the underlying transport. 84 | func connect() 85 | 86 | /// Connects a socket through this manager's engine. 87 | /// 88 | /// - parameter socket: The socket who we should connect through this manager. 89 | func connectSocket(_ socket: SocketIOClient) 90 | 91 | /// Called when the manager has disconnected from socket.io. 92 | /// 93 | /// - parameter reason: The reason for the disconnection. 94 | func didDisconnect(reason: String) 95 | 96 | /// Disconnects the manager and all associated sockets. 97 | func disconnect() 98 | 99 | /// Disconnects the given socket. 100 | /// 101 | /// - parameter socket: The socket to disconnect. 102 | func disconnectSocket(_ socket: SocketIOClient) 103 | 104 | /// Disconnects the socket associated with `forNamespace`. 105 | /// 106 | /// - parameter nsp: The namespace to disconnect from. 107 | func disconnectSocket(forNamespace nsp: String) 108 | 109 | /// Sends an event to the server on all namespaces in this manager. 110 | /// 111 | /// - parameter event: The event to send. 112 | /// - parameter items: The data to send with this event. 113 | func emitAll(_ event: String, withItems items: [Any]) 114 | 115 | /// Tries to reconnect to the server. 116 | /// 117 | /// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event. 118 | func reconnect() 119 | 120 | /// Removes the socket from the manager's control. 121 | /// After calling this method the socket should no longer be considered usable. 122 | /// 123 | /// - parameter socket: The socket to remove. 124 | /// - returns: The socket removed, if it was owned by the manager. 125 | @discardableResult 126 | func removeSocket(_ socket: SocketIOClient) -> SocketIOClient? 127 | 128 | /// Returns a `SocketIOClient` for the given namespace. This socket shares a transport with the manager. 129 | /// 130 | /// Calling multiple times returns the same socket. 131 | /// 132 | /// Sockets created from this method are retained by the manager. 133 | /// Call one of the `disconnectSocket` methods on the implementing class to remove the socket from manager control. 134 | /// Or call `SocketIOClient.disconnect()` on the client. 135 | /// 136 | /// - parameter nsp: The namespace for the socket. 137 | /// - returns: A `SocketIOClient` for the given namespace. 138 | func socket(forNamespace nsp: String) -> SocketIOClient 139 | } 140 | -------------------------------------------------------------------------------- /Source/SocketIO/Parse/SocketPacket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketPacket.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 1/18/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import Foundation 27 | 28 | /// A struct that represents a socket.io packet. 29 | public struct SocketPacket : CustomStringConvertible { 30 | // MARK: Properties 31 | 32 | private static let logType = "SocketPacket" 33 | 34 | /// The namespace for this packet. 35 | public let nsp: String 36 | 37 | /// If > 0 then this packet is using acking. 38 | public let id: Int 39 | 40 | /// The type of this packet. 41 | public let type: PacketType 42 | 43 | /// An array of binary data for this packet. 44 | public internal(set) var binary: [Data] 45 | 46 | /// The data for this event. 47 | /// 48 | /// Note: This includes all data inside of the socket.io packet payload array, which includes the event name for 49 | /// event type packets. 50 | public internal(set) var data: [Any] 51 | 52 | /// Returns the payload for this packet, minus the event name if this is an event or binaryEvent type packet. 53 | public var args: [Any] { 54 | if type == .event || type == .binaryEvent && data.count != 0 { 55 | return Array(data.dropFirst()) 56 | } else { 57 | return data 58 | } 59 | } 60 | 61 | private let placeholders: Int 62 | 63 | /// A string representation of this packet. 64 | public var description: String { 65 | return "SocketPacket {type: \(String(type.rawValue)); data: " + 66 | "\(String(describing: data)); id: \(id); placeholders: \(placeholders); nsp: \(nsp)}" 67 | } 68 | 69 | /// The event name for this packet. 70 | public var event: String { 71 | return String(describing: data[0]) 72 | } 73 | 74 | /// A string representation of this packet. 75 | public var packetString: String { 76 | return createPacketString() 77 | } 78 | 79 | init(type: PacketType, data: [Any] = [Any](), id: Int = -1, nsp: String, placeholders: Int = 0, 80 | binary: [Data] = [Data]()) { 81 | self.data = data 82 | self.id = id 83 | self.nsp = nsp 84 | self.type = type 85 | self.placeholders = placeholders 86 | self.binary = binary 87 | } 88 | 89 | mutating func addData(_ data: Data) -> Bool { 90 | if placeholders == binary.count { 91 | return true 92 | } 93 | 94 | binary.append(data) 95 | 96 | if placeholders == binary.count { 97 | fillInPlaceholders() 98 | return true 99 | } else { 100 | return false 101 | } 102 | } 103 | 104 | private func completeMessage(_ message: String) -> String { 105 | guard data.count != 0 else { return message + "[]" } 106 | guard let jsonSend = try? data.toJSON(), let jsonString = String(data: jsonSend, encoding: .utf8) else { 107 | DefaultSocketLogger.Logger.error("Error creating JSON object in SocketPacket.completeMessage", 108 | type: SocketPacket.logType) 109 | 110 | return message + "[]" 111 | } 112 | 113 | return message + jsonString 114 | } 115 | 116 | private func createPacketString() -> String { 117 | let typeString = String(type.rawValue) 118 | // Binary count? 119 | let binaryCountString = typeString + (type.isBinary ? "\(String(binary.count))-" : "") 120 | // Namespace? 121 | let nspString = binaryCountString + (nsp != "/" ? "\(nsp)," : "") 122 | // Ack number? 123 | let idString = nspString + (id != -1 ? String(id) : "") 124 | 125 | return completeMessage(idString) 126 | } 127 | 128 | // Called when we have all the binary data for a packet 129 | // calls _fillInPlaceholders, which replaces placeholders with the 130 | // corresponding binary 131 | private mutating func fillInPlaceholders() { 132 | data = data.map(_fillInPlaceholders) 133 | } 134 | 135 | // Helper method that looks for placeholders 136 | // If object is a collection it will recurse 137 | // Returns the object if it is not a placeholder or the corresponding 138 | // binary data 139 | private func _fillInPlaceholders(_ object: Any) -> Any { 140 | switch object { 141 | case let dict as JSON: 142 | if dict["_placeholder"] as? Bool ?? false { 143 | return binary[dict["num"] as! Int] 144 | } else { 145 | return dict.reduce(into: JSON(), {cur, keyValue in 146 | cur[keyValue.0] = _fillInPlaceholders(keyValue.1) 147 | }) 148 | } 149 | case let arr as [Any]: 150 | return arr.map(_fillInPlaceholders) 151 | default: 152 | return object 153 | } 154 | } 155 | } 156 | 157 | public extension SocketPacket { 158 | // MARK: PacketType enum 159 | 160 | /// The type of packets. 161 | public enum PacketType: Int { 162 | // MARK: Cases 163 | 164 | /// Connect: 0 165 | case connect 166 | 167 | /// Disconnect: 1 168 | case disconnect 169 | 170 | /// Event: 2 171 | case event 172 | 173 | /// Ack: 3 174 | case ack 175 | 176 | /// Error: 4 177 | case error 178 | 179 | /// Binary Event: 5 180 | case binaryEvent 181 | 182 | /// Binary Ack: 6 183 | case binaryAck 184 | 185 | // MARK: Properties 186 | 187 | /// Whether or not this type is binary 188 | public var isBinary: Bool { 189 | return self == .binaryAck || self == .binaryEvent 190 | } 191 | } 192 | } 193 | 194 | extension SocketPacket { 195 | private static func findType(_ binCount: Int, ack: Bool) -> PacketType { 196 | switch binCount { 197 | case 0 where !ack: 198 | return .event 199 | case 0 where ack: 200 | return .ack 201 | case _ where !ack: 202 | return .binaryEvent 203 | case _ where ack: 204 | return .binaryAck 205 | default: 206 | return .error 207 | } 208 | } 209 | 210 | static func packetFromEmit(_ items: [Any], id: Int, nsp: String, ack: Bool, checkForBinary: Bool = true) -> SocketPacket { 211 | if checkForBinary { 212 | let (parsedData, binary) = deconstructData(items) 213 | 214 | return SocketPacket(type: findType(binary.count, ack: ack), data: parsedData, id: id, nsp: nsp, 215 | binary: binary) 216 | } else { 217 | return SocketPacket(type: findType(0, ack: ack), data: items, id: id, nsp: nsp) 218 | } 219 | } 220 | } 221 | 222 | private extension SocketPacket { 223 | // Recursive function that looks for NSData in collections 224 | static func shred(_ data: Any, binary: inout [Data]) -> Any { 225 | let placeholder = ["_placeholder": true, "num": binary.count] as JSON 226 | 227 | switch data { 228 | case let bin as Data: 229 | binary.append(bin) 230 | 231 | return placeholder 232 | case let arr as [Any]: 233 | return arr.map({shred($0, binary: &binary)}) 234 | case let dict as JSON: 235 | return dict.reduce(into: JSON(), {cur, keyValue in 236 | cur[keyValue.0] = shred(keyValue.1, binary: &binary) 237 | }) 238 | default: 239 | return data 240 | } 241 | } 242 | 243 | // Removes binary data from emit data 244 | // Returns a type containing the de-binaryed data and the binary 245 | static func deconstructData(_ data: [Any]) -> ([Any], [Data]) { 246 | var binary = [Data]() 247 | 248 | return (data.map({ shred($0, binary: &binary) }), binary) 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Source/SocketIO/Parse/SocketParsable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketParsable.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | import Foundation 24 | 25 | /// Defines that a type will be able to parse socket.io-protocol messages. 26 | public protocol SocketParsable : AnyObject { 27 | // MARK: Methods 28 | 29 | /// Called when the engine has received some binary data that should be attached to a packet. 30 | /// 31 | /// Packets binary data should be sent directly after the packet that expects it, so there's confusion over 32 | /// where the data should go. Data should be received in the order it is sent, so that the correct data is put 33 | /// into the correct placeholder. 34 | /// 35 | /// - parameter data: The data that should be attached to a packet. 36 | func parseBinaryData(_ data: Data) -> SocketPacket? 37 | 38 | /// Called when the engine has received a string that should be parsed into a socket.io packet. 39 | /// 40 | /// - parameter message: The string that needs parsing. 41 | /// - returns: A completed socket packet if there is no more data left to collect. 42 | func parseSocketMessage(_ message: String) -> SocketPacket? 43 | } 44 | 45 | /// Errors that can be thrown during parsing. 46 | public enum SocketParsableError : Error { 47 | // MARK: Cases 48 | 49 | /// Thrown when a packet received has an invalid data array, or is missing the data array. 50 | case invalidDataArray 51 | 52 | /// Thrown when an malformed packet is received. 53 | case invalidPacket 54 | 55 | /// Thrown when the parser receives an unknown packet type. 56 | case invalidPacketType 57 | } 58 | 59 | /// Says that a type will be able to buffer binary data before all data for an event has come in. 60 | public protocol SocketDataBufferable : AnyObject { 61 | // MARK: Properties 62 | 63 | /// A list of packets that are waiting for binary data. 64 | /// 65 | /// The way that socket.io works all data should be sent directly after each packet. 66 | /// So this should ideally be an array of one packet waiting for data. 67 | /// 68 | /// **This should not be modified directly.** 69 | var waitingPackets: [SocketPacket] { get set } 70 | } 71 | 72 | public extension SocketParsable where Self: SocketManagerSpec & SocketDataBufferable { 73 | /// Parses a message from the engine, returning a complete SocketPacket or throwing. 74 | /// 75 | /// - parameter message: The message to parse. 76 | /// - returns: A completed packet, or throwing. 77 | internal func parseString(_ message: String) throws -> SocketPacket { 78 | var reader = SocketStringReader(message: message) 79 | 80 | guard let type = Int(reader.read(count: 1)).flatMap({ SocketPacket.PacketType(rawValue: $0) }) else { 81 | throw SocketParsableError.invalidPacketType 82 | } 83 | 84 | if !reader.hasNext { 85 | return SocketPacket(type: type, nsp: "/") 86 | } 87 | 88 | var namespace = "/" 89 | var placeholders = -1 90 | 91 | if type.isBinary { 92 | if let holders = Int(reader.readUntilOccurence(of: "-")) { 93 | placeholders = holders 94 | } else { 95 | throw SocketParsableError.invalidPacket 96 | } 97 | } 98 | 99 | if reader.currentCharacter == "/" { 100 | namespace = reader.readUntilOccurence(of: ",") 101 | } 102 | 103 | if !reader.hasNext { 104 | return SocketPacket(type: type, nsp: namespace, placeholders: placeholders) 105 | } 106 | 107 | var idString = "" 108 | 109 | if type == .error { 110 | reader.advance(by: -1) 111 | } else { 112 | while let int = Int(reader.read(count: 1)) { 113 | idString += String(int) 114 | } 115 | 116 | reader.advance(by: -2) 117 | } 118 | 119 | var dataArray = String(message.utf16[message.utf16.index(reader.currentIndex, offsetBy: 1)...])! 120 | 121 | if type == .error && !dataArray.hasPrefix("[") && !dataArray.hasSuffix("]") { 122 | dataArray = "[" + dataArray + "]" 123 | } 124 | 125 | let data = try parseData(dataArray) 126 | 127 | return SocketPacket(type: type, data: data, id: Int(idString) ?? -1, nsp: namespace, placeholders: placeholders) 128 | } 129 | 130 | // Parses data for events 131 | private func parseData(_ data: String) throws -> [Any] { 132 | do { 133 | return try data.toArray() 134 | } catch { 135 | throw SocketParsableError.invalidDataArray 136 | } 137 | } 138 | 139 | /// Called when the engine has received a string that should be parsed into a socket.io packet. 140 | /// 141 | /// - parameter message: The string that needs parsing. 142 | /// - returns: A completed socket packet or nil if the packet is invalid. 143 | public func parseSocketMessage(_ message: String) -> SocketPacket? { 144 | guard !message.isEmpty else { return nil } 145 | 146 | DefaultSocketLogger.Logger.log("Parsing \(message)", type: "SocketParser") 147 | 148 | do { 149 | let packet = try parseString(message) 150 | 151 | DefaultSocketLogger.Logger.log("Decoded packet as: \(packet.description)", type: "SocketParser") 152 | 153 | return packet 154 | } catch { 155 | DefaultSocketLogger.Logger.error("\(error): \(message)", type: "SocketParser") 156 | 157 | return nil 158 | } 159 | } 160 | 161 | /// Called when the engine has received some binary data that should be attached to a packet. 162 | /// 163 | /// Packets binary data should be sent directly after the packet that expects it, so there's confusion over 164 | /// where the data should go. Data should be received in the order it is sent, so that the correct data is put 165 | /// into the correct placeholder. 166 | /// 167 | /// - parameter data: The data that should be attached to a packet. 168 | /// - returns: A completed socket packet if there is no more data left to collect. 169 | public func parseBinaryData(_ data: Data) -> SocketPacket? { 170 | guard !waitingPackets.isEmpty else { 171 | DefaultSocketLogger.Logger.error("Got data when not remaking packet", type: "SocketParser") 172 | 173 | return nil 174 | } 175 | 176 | // Should execute event? 177 | guard waitingPackets[waitingPackets.count - 1].addData(data) else { return nil } 178 | 179 | return waitingPackets.removeLast() 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Source/SocketIO/Util/SSLSecurity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSLSecurity.swift 3 | // SocketIO-iOS 4 | // 5 | // Created by Lukas Schmidt on 24.09.17. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import Starscream 27 | 28 | /// A wrapper around Starscream's SSLSecurity that provides a minimal Objective-C interface. 29 | open class SSLSecurity : NSObject { 30 | // MARK: Properties 31 | 32 | /// The internal Starscream SSLSecurity. 33 | public let security: Starscream.SSLSecurity 34 | 35 | init(security: Starscream.SSLSecurity) { 36 | self.security = security 37 | } 38 | 39 | // MARK: Methods 40 | 41 | /// Creates a new SSLSecurity that specifies whether to use publicKeys or certificates should be used for SSL 42 | /// pinning validation 43 | /// 44 | /// - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning 45 | /// validation 46 | @objc 47 | public convenience init(usePublicKeys: Bool = true) { 48 | let security = Starscream.SSLSecurity(usePublicKeys: usePublicKeys) 49 | self.init(security: security) 50 | } 51 | 52 | 53 | /// Designated init 54 | /// 55 | /// - parameter certs: is the certificates or public keys to use 56 | /// - parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning 57 | /// validation 58 | /// - returns: a representation security object to be used with 59 | public convenience init(certs: [SSLCert], usePublicKeys: Bool) { 60 | let security = Starscream.SSLSecurity(certs: certs, usePublicKeys: usePublicKeys) 61 | self.init(security: security) 62 | } 63 | 64 | /// Returns whether or not the given trust is valid. 65 | /// 66 | /// - parameter trust: The trust to validate. 67 | /// - parameter domain: The CN domain to validate. 68 | /// - returns: Whether or not this is valid. 69 | public func isValid(_ trust: SecTrust, domain: String?) -> Bool { 70 | return security.isValid(trust, domain: domain) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Source/SocketIO/Util/SocketExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketExtensions.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 7/1/2016. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | import Starscream 27 | 28 | enum JSONError : Error { 29 | case notArray 30 | case notNSDictionary 31 | } 32 | 33 | extension Array { 34 | func toJSON() throws -> Data { 35 | return try JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions(rawValue: 0)) 36 | } 37 | } 38 | 39 | extension CharacterSet { 40 | static var allowedURLCharacterSet: CharacterSet { 41 | return CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[]\" {}^").inverted 42 | } 43 | } 44 | 45 | extension Dictionary where Key == String, Value == Any { 46 | private static func keyValueToSocketIOClientOption(key: String, value: Any) -> SocketIOClientOption? { 47 | switch (key, value) { 48 | case let ("connectParams", params as [String: Any]): 49 | return .connectParams(params) 50 | case let ("cookies", cookies as [HTTPCookie]): 51 | return .cookies(cookies) 52 | case let ("extraHeaders", headers as [String: String]): 53 | return .extraHeaders(headers) 54 | case let ("forceNew", force as Bool): 55 | return .forceNew(force) 56 | case let ("forcePolling", force as Bool): 57 | return .forcePolling(force) 58 | case let ("forceWebsockets", force as Bool): 59 | return .forceWebsockets(force) 60 | case let ("handleQueue", queue as DispatchQueue): 61 | return .handleQueue(queue) 62 | case let ("log", log as Bool): 63 | return .log(log) 64 | case let ("logger", logger as SocketLogger): 65 | return .logger(logger) 66 | case let ("path", path as String): 67 | return .path(path) 68 | case let ("reconnects", reconnects as Bool): 69 | return .reconnects(reconnects) 70 | case let ("reconnectAttempts", attempts as Int): 71 | return .reconnectAttempts(attempts) 72 | case let ("reconnectWait", wait as Int): 73 | return .reconnectWait(wait) 74 | case let ("secure", secure as Bool): 75 | return .secure(secure) 76 | case let ("security", security as SSLSecurity): 77 | return .security(security) 78 | case let ("selfSigned", selfSigned as Bool): 79 | return .selfSigned(selfSigned) 80 | case let ("sessionDelegate", delegate as URLSessionDelegate): 81 | return .sessionDelegate(delegate) 82 | case let ("compress", compress as Bool): 83 | return compress ? .compress : nil 84 | default: 85 | return nil 86 | } 87 | } 88 | 89 | func toSocketConfiguration() -> SocketIOClientConfiguration { 90 | var options = [] as SocketIOClientConfiguration 91 | 92 | for (rawKey, value) in self { 93 | if let opt = Dictionary.keyValueToSocketIOClientOption(key: rawKey, value: value) { 94 | options.insert(opt) 95 | } 96 | } 97 | 98 | return options 99 | } 100 | } 101 | 102 | extension String { 103 | func toArray() throws -> [Any] { 104 | guard let stringData = data(using: .utf16, allowLossyConversion: false) else { return [] } 105 | guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else { 106 | throw JSONError.notArray 107 | } 108 | 109 | return array 110 | } 111 | 112 | func toDictionary() throws -> [String: Any] { 113 | guard let binData = data(using: .utf16, allowLossyConversion: false) else { return [:] } 114 | guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? [String: Any] else { 115 | throw JSONError.notNSDictionary 116 | } 117 | 118 | return json 119 | } 120 | 121 | func urlEncode() -> String? { 122 | return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Source/SocketIO/Util/SocketLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketLogger.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/11/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// Represents a class will log client events. 28 | public protocol SocketLogger : AnyObject { 29 | // MARK: Properties 30 | 31 | /// Whether to log or not 32 | var log: Bool { get set } 33 | 34 | // MARK: Methods 35 | 36 | /// Normal log messages 37 | /// 38 | /// - parameter message: The message being logged. Can include `%@` that will be replaced with `args` 39 | /// - parameter type: The type of entity that called for logging. 40 | /// - parameter args: Any args that should be inserted into the message. May be left out. 41 | func log(_ message: @autoclosure () -> String, type: String) 42 | 43 | /// Error Messages 44 | /// 45 | /// - parameter message: The message being logged. Can include `%@` that will be replaced with `args` 46 | /// - parameter type: The type of entity that called for logging. 47 | /// - parameter args: Any args that should be inserted into the message. May be left out. 48 | func error(_ message: @autoclosure () -> String, type: String) 49 | } 50 | 51 | public extension SocketLogger { 52 | /// Default implementation. 53 | func log(_ message: @autoclosure () -> String, type: String) { 54 | abstractLog("LOG", message: message, type: type) 55 | } 56 | 57 | /// Default implementation. 58 | func error(_ message: @autoclosure () -> String, type: String) { 59 | abstractLog("ERROR", message: message, type: type) 60 | } 61 | 62 | private func abstractLog(_ logType: String, message: @autoclosure () -> String, type: String) { 63 | guard log else { return } 64 | 65 | NSLog("\(logType) \(type): %@", message()) 66 | } 67 | } 68 | 69 | class DefaultSocketLogger : SocketLogger { 70 | static var Logger: SocketLogger = DefaultSocketLogger() 71 | 72 | var log = false 73 | } 74 | -------------------------------------------------------------------------------- /Source/SocketIO/Util/SocketStringReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketStringReader.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Lukas Schmidt on 07.09.15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | struct SocketStringReader { 26 | let message: String 27 | var currentIndex: String.UTF16View.Index 28 | var hasNext: Bool { 29 | return currentIndex != message.utf16.endIndex 30 | } 31 | 32 | var currentCharacter: String { 33 | return String(UnicodeScalar(message.utf16[currentIndex])!) 34 | } 35 | 36 | init(message: String) { 37 | self.message = message 38 | currentIndex = message.utf16.startIndex 39 | } 40 | 41 | @discardableResult 42 | mutating func advance(by: Int) -> String.UTF16View.Index { 43 | currentIndex = message.utf16.index(currentIndex, offsetBy: by) 44 | 45 | return currentIndex 46 | } 47 | 48 | mutating func read(count: Int) -> String { 49 | let readString = String(message.utf16[currentIndex.. String { 57 | let substring = message.utf16[currentIndex...] 58 | 59 | guard let foundIndex = substring.index(of: string.utf16.first!) else { 60 | currentIndex = message.utf16.endIndex 61 | 62 | return String(substring)! 63 | } 64 | 65 | advance(by: substring.distance(from: substring.startIndex, to: foundIndex) + 1) 66 | 67 | return String(substring[substring.startIndex.. String { 71 | return read(count: message.utf16.distance(from: currentIndex, to: message.utf16.endIndex)) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Source/SocketIO/Util/SocketTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketTypes.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 4/8/15. 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import Foundation 26 | 27 | /// A marking protocol that says a type can be represented in a socket.io packet. 28 | /// 29 | /// Example: 30 | /// 31 | /// ```swift 32 | /// struct CustomData : SocketData { 33 | /// let name: String 34 | /// let age: Int 35 | /// 36 | /// func socketRepresentation() -> SocketData { 37 | /// return ["name": name, "age": age] 38 | /// } 39 | /// } 40 | /// 41 | /// socket.emit("myEvent", CustomData(name: "Erik", age: 24)) 42 | /// ``` 43 | public protocol SocketData { 44 | // MARK: Methods 45 | 46 | /// A representation of self that can sent over socket.io. 47 | func socketRepresentation() throws -> SocketData 48 | } 49 | 50 | public extension SocketData { 51 | /// Default implementation. Only works for native Swift types and a few Foundation types. 52 | func socketRepresentation() -> SocketData { 53 | return self 54 | } 55 | } 56 | 57 | extension Array : SocketData { } 58 | extension Bool : SocketData { } 59 | extension Dictionary : SocketData { } 60 | extension Double : SocketData { } 61 | extension Int : SocketData { } 62 | extension NSArray : SocketData { } 63 | extension Data : SocketData { } 64 | extension NSData : SocketData { } 65 | extension NSDictionary : SocketData { } 66 | extension NSString : SocketData { } 67 | extension NSNull : SocketData { } 68 | extension String : SocketData { } 69 | 70 | /// A typealias for an ack callback. 71 | public typealias AckCallback = ([Any]) -> () 72 | 73 | /// A typealias for a normal callback. 74 | public typealias NormalCallback = ([Any], SocketAckEmitter) -> () 75 | 76 | typealias JSON = [String: Any] 77 | typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data]) 78 | typealias ProbeWaitQueue = [Probe] 79 | 80 | enum Either { 81 | case left(E) 82 | case right(V) 83 | } 84 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketAckManagerTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketAckManagerTest.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Lukas Schmidt on 04.09.15. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import SocketIO 11 | 12 | class SocketAckManagerTest : XCTestCase { 13 | var ackManager = SocketAckManager() 14 | 15 | func testAddAcks() { 16 | let callbackExpection = expectation(description: "callbackExpection") 17 | let itemsArray = ["Hi", "ho"] 18 | 19 | func callback(_ items: [Any]) { 20 | callbackExpection.fulfill() 21 | } 22 | 23 | ackManager.addAck(1, callback: callback) 24 | ackManager.executeAck(1, with: itemsArray) 25 | 26 | waitForExpectations(timeout: 3.0, handler: nil) 27 | } 28 | 29 | func testManagerTimeoutAck() { 30 | let callbackExpection = expectation(description: "Manager should timeout ack with noAck status") 31 | let itemsArray = ["Hi", "ho"] 32 | 33 | func callback(_ items: [Any]) { 34 | XCTAssertEqual(items.count, 1, "Timed out ack should have one value") 35 | guard let timeoutReason = items[0] as? String else { 36 | XCTFail("Timeout reason should be a string") 37 | 38 | return 39 | } 40 | 41 | XCTAssertEqual(timeoutReason, SocketAckStatus.noAck.rawValue) 42 | 43 | callbackExpection.fulfill() 44 | } 45 | 46 | ackManager.addAck(1, callback: callback) 47 | ackManager.timeoutAck(1) 48 | 49 | waitForExpectations(timeout: 0.2, handler: nil) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketBasicPacketTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketBasicPacketTest.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/7/15. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import SocketIO 11 | 12 | class SocketBasicPacketTest : XCTestCase { 13 | func testEmptyEmit() { 14 | let sendData = ["test"] 15 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 16 | let parsed = parser.parseSocketMessage(packetStr)! 17 | 18 | XCTAssertEqual(parsed.type, .event) 19 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 20 | } 21 | 22 | func testNullEmit() { 23 | let sendData: [Any] = ["test", NSNull()] 24 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 25 | let parsed = parser.parseSocketMessage(packetStr)! 26 | 27 | XCTAssertEqual(parsed.type, .event) 28 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 29 | } 30 | 31 | func testStringEmit() { 32 | let sendData = ["test", "foo bar"] 33 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 34 | let parsed = parser.parseSocketMessage(packetStr)! 35 | 36 | XCTAssertEqual(parsed.type, .event) 37 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 38 | } 39 | 40 | func testStringEmitWithQuotes() { 41 | let sendData = ["test", "\"he\"llo world\""] 42 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 43 | let parsed = parser.parseSocketMessage(packetStr)! 44 | 45 | XCTAssertEqual(parsed.type, .event) 46 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 47 | } 48 | 49 | func testJSONEmit() { 50 | let sendData: [Any] = ["test", ["foobar": true, "hello": 1, "test": "hello", "null": NSNull()]] 51 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 52 | let parsed = parser.parseSocketMessage(packetStr)! 53 | 54 | XCTAssertEqual(parsed.type, .event) 55 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 56 | } 57 | 58 | func testArrayEmit() { 59 | let sendData: [Any] = ["test", ["hello", 1, ["test": "test"]]] 60 | let packetStr = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false).packetString 61 | let parsed = parser.parseSocketMessage(packetStr)! 62 | 63 | XCTAssertEqual(parsed.type, .event) 64 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 65 | } 66 | 67 | func testBinaryEmit() { 68 | let sendData: [Any] = ["test", data] 69 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false) 70 | let parsed = parser.parseSocketMessage(packet.packetString)! 71 | 72 | XCTAssertEqual(parsed.type, .binaryEvent) 73 | XCTAssertEqual(packet.binary, [data]) 74 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 75 | "test", 76 | ["_placeholder": true, "num": 0] 77 | ])) 78 | } 79 | 80 | func testMultipleBinaryEmit() { 81 | let sendData: [Any] = ["test", ["data1": data, "data2": data2] as NSDictionary] 82 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/", ack: false) 83 | let parsed = parser.parseSocketMessage(packet.packetString)! 84 | 85 | XCTAssertEqual(parsed.type, .binaryEvent) 86 | 87 | let binaryObj = parsed.data[1] as! [String: Any] 88 | let data1Loc = (binaryObj["data1"] as! [String: Any])["num"] as! Int 89 | let data2Loc = (binaryObj["data2"] as! [String: Any])["num"] as! Int 90 | 91 | XCTAssertEqual(packet.binary[data1Loc], data) 92 | XCTAssertEqual(packet.binary[data2Loc], data2) 93 | } 94 | 95 | func testEmitWithAck() { 96 | let sendData = ["test"] 97 | let packetStr = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: false).packetString 98 | let parsed = parser.parseSocketMessage(packetStr)! 99 | 100 | XCTAssertEqual(parsed.type, .event) 101 | XCTAssertEqual(parsed.id, 0) 102 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 103 | } 104 | 105 | func testEmitDataWithAck() { 106 | let sendData: [Any] = ["test", data] 107 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: false) 108 | let parsed = parser.parseSocketMessage(packet.packetString)! 109 | 110 | XCTAssertEqual(parsed.type, .binaryEvent) 111 | XCTAssertEqual(parsed.id, 0) 112 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 113 | "test", 114 | ["_placeholder": true, "num": 0] 115 | ])) 116 | XCTAssertEqual(packet.binary, [data]) 117 | } 118 | 119 | // Acks 120 | func testEmptyAck() { 121 | let packetStr = SocketPacket.packetFromEmit([], id: 0, nsp: "/", ack: true).packetString 122 | let parsed = parser.parseSocketMessage(packetStr)! 123 | 124 | XCTAssertEqual(parsed.type, .ack) 125 | XCTAssertEqual(parsed.id, 0) 126 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [])) 127 | } 128 | 129 | func testNullAck() { 130 | let sendData = [NSNull()] 131 | let packetStr = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: true).packetString 132 | let parsed = parser.parseSocketMessage(packetStr)! 133 | 134 | XCTAssertEqual(parsed.type, .ack) 135 | XCTAssertEqual(parsed.id, 0) 136 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 137 | } 138 | 139 | func testStringAck() { 140 | let sendData = ["test"] 141 | let packetStr = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: true).packetString 142 | let parsed = parser.parseSocketMessage(packetStr)! 143 | 144 | XCTAssertEqual(parsed.type, .ack) 145 | XCTAssertEqual(parsed.id, 0) 146 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 147 | } 148 | 149 | func testJSONAck() { 150 | let sendData = [["foobar": true, "hello": 1, "test": "hello", "null": NSNull()]] 151 | let packetStr = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: true).packetString 152 | let parsed = parser.parseSocketMessage(packetStr)! 153 | 154 | XCTAssertEqual(parsed.type, .ack) 155 | XCTAssertEqual(parsed.id, 0) 156 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 157 | } 158 | 159 | func testBinaryAck() { 160 | let sendData = [data] 161 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: true) 162 | let parsed = parser.parseSocketMessage(packet.packetString)! 163 | 164 | XCTAssertEqual(parsed.type, .binaryAck) 165 | XCTAssertEqual(packet.binary, [data]) 166 | XCTAssertEqual(parsed.id, 0) 167 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 168 | ["_placeholder": true, "num": 0] 169 | ])) 170 | } 171 | 172 | func testMultipleBinaryAck() { 173 | let sendData = [["data1": data, "data2": data2]] 174 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/", ack: true) 175 | let parsed = parser.parseSocketMessage(packet.packetString)! 176 | 177 | XCTAssertEqual(parsed.id, 0) 178 | XCTAssertEqual(parsed.type, .binaryAck) 179 | 180 | let binaryObj = parsed.data[0] as! [String: Any] 181 | let data1Loc = (binaryObj["data1"] as! [String: Any])["num"] as! Int 182 | let data2Loc = (binaryObj["data2"] as! [String: Any])["num"] as! Int 183 | 184 | XCTAssertEqual(packet.binary[data1Loc], data) 185 | XCTAssertEqual(packet.binary[data2Loc], data2) 186 | } 187 | 188 | func testBinaryStringPlaceholderInMessage() { 189 | let engineString = "52-[\"test\",\"~~0\",{\"num\":0,\"_placeholder\":true},{\"_placeholder\":true,\"num\":1}]" 190 | let manager = SocketManager(socketURL: URL(string: "http://localhost/")!) 191 | 192 | var packet = try! manager.parseString(engineString) 193 | 194 | XCTAssertEqual(packet.event, "test") 195 | _ = packet.addData(data) 196 | _ = packet.addData(data2) 197 | XCTAssertEqual(packet.args[0] as? String, "~~0") 198 | } 199 | 200 | private func compareAnyArray(input: [Any], expected: [Any]) -> Bool { 201 | guard input.count == expected.count else { return false } 202 | 203 | return (input as NSArray).isEqual(to: expected) 204 | } 205 | 206 | let data = "test".data(using: String.Encoding.utf8)! 207 | let data2 = "test2".data(using: String.Encoding.utf8)! 208 | var parser: SocketParsable! 209 | 210 | override func setUp() { 211 | super.setUp() 212 | 213 | parser = SocketManager(socketURL: URL(string: "http://localhost")!) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketEngineTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketEngineTest.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/15/15. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import SocketIO 11 | 12 | class SocketEngineTest: XCTestCase { 13 | func testBasicPollingMessage() { 14 | let expect = expectation(description: "Basic polling test") 15 | socket.on("blankTest") {data, ack in 16 | expect.fulfill() 17 | } 18 | 19 | engine.parsePollingMessage("15:42[\"blankTest\"]") 20 | waitForExpectations(timeout: 3, handler: nil) 21 | } 22 | 23 | func testTwoPacketsInOnePollTest() { 24 | let finalExpectation = expectation(description: "Final packet in poll test") 25 | var gotBlank = false 26 | 27 | socket.on("blankTest") {data, ack in 28 | gotBlank = true 29 | } 30 | 31 | socket.on("stringTest") {data, ack in 32 | if let str = data[0] as? String, gotBlank { 33 | if str == "hello" { 34 | finalExpectation.fulfill() 35 | } 36 | } 37 | } 38 | 39 | engine.parsePollingMessage("15:42[\"blankTest\"]24:42[\"stringTest\",\"hello\"]") 40 | waitForExpectations(timeout: 3, handler: nil) 41 | } 42 | 43 | func testEngineDoesErrorOnUnknownTransport() { 44 | let finalExpectation = expectation(description: "Unknown Transport") 45 | 46 | socket.on("error") {data, ack in 47 | if let error = data[0] as? String, error == "Unknown transport" { 48 | finalExpectation.fulfill() 49 | } 50 | } 51 | 52 | engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}") 53 | waitForExpectations(timeout: 3, handler: nil) 54 | } 55 | 56 | func testEngineDoesErrorOnUnknownMessage() { 57 | let finalExpectation = expectation(description: "Engine Errors") 58 | 59 | socket.on("error") {data, ack in 60 | finalExpectation.fulfill() 61 | } 62 | 63 | engine.parseEngineMessage("afafafda") 64 | waitForExpectations(timeout: 3, handler: nil) 65 | } 66 | 67 | func testEngineDecodesUTF8Properly() { 68 | let expect = expectation(description: "Engine Decodes utf8") 69 | 70 | socket.on("stringTest") {data, ack in 71 | XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo𦅙𦅛", "Failed string test") 72 | expect.fulfill() 73 | } 74 | 75 | let stringMessage = "42[\"stringTest\",\"lïne one\\nlīne \\rtwo𦅙𦅛\"]" 76 | 77 | engine.parsePollingMessage("\(stringMessage.utf16.count):\(stringMessage)") 78 | waitForExpectations(timeout: 3, handler: nil) 79 | } 80 | 81 | func testEncodeURLProperly() { 82 | engine.connectParams = [ 83 | "created": "2016-05-04T18:31:15+0200" 84 | ] 85 | 86 | XCTAssertEqual(engine.urlPolling.query, "transport=polling&b64=1&created=2016-05-04T18%3A31%3A15%2B0200") 87 | XCTAssertEqual(engine.urlWebSocket.query, "transport=websocket&created=2016-05-04T18%3A31%3A15%2B0200") 88 | 89 | engine.connectParams = [ 90 | "forbidden": "!*'();:@&=+$,/?%#[]\" {}^" 91 | ] 92 | 93 | 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%5E") 94 | 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%5E") 95 | } 96 | 97 | func testBase64Data() { 98 | let expect = expectation(description: "Engine Decodes base64 data") 99 | let b64String = "b4aGVsbG8NCg==" 100 | let packetString = "451-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]" 101 | 102 | socket.on("test") {data, ack in 103 | if let data = data[0] as? Data, let string = String(data: data, encoding: .utf8) { 104 | XCTAssertEqual(string, "hello") 105 | } 106 | 107 | expect.fulfill() 108 | } 109 | 110 | engine.parseEngineMessage(packetString) 111 | engine.parseEngineMessage(b64String) 112 | 113 | waitForExpectations(timeout: 3, handler: nil) 114 | } 115 | 116 | func testSettingExtraHeadersBeforeConnectSetsEngineExtraHeaders() { 117 | let newValue = ["hello": "world"] 118 | 119 | manager.engine = engine 120 | manager.setTestStatus(.notConnected) 121 | manager.config = [.extraHeaders(["new": "value"])] 122 | manager.config.insert(.extraHeaders(newValue), replacing: true) 123 | 124 | XCTAssertEqual(2, manager.config.count) 125 | XCTAssertEqual(manager.engine!.extraHeaders!, newValue) 126 | 127 | for config in manager.config { 128 | switch config { 129 | case let .extraHeaders(headers): 130 | XCTAssertTrue(headers.keys.contains("hello"), "It should contain hello header key") 131 | XCTAssertFalse(headers.keys.contains("new"), "It should not contain old data") 132 | case .path: 133 | continue 134 | default: 135 | XCTFail("It should only have two configs") 136 | } 137 | } 138 | } 139 | 140 | func testSettingExtraHeadersAfterConnectDoesNotIgnoreChanges() { 141 | let newValue = ["hello": "world"] 142 | 143 | manager.engine = engine 144 | manager.setTestStatus(.connected) 145 | engine.setConnected(true) 146 | manager.config = [.extraHeaders(["new": "value"])] 147 | manager.config.insert(.extraHeaders(["hello": "world"]), replacing: true) 148 | 149 | XCTAssertEqual(2, manager.config.count) 150 | XCTAssertEqual(manager.engine!.extraHeaders!, newValue) 151 | } 152 | 153 | func testSettingPathAfterConnectDoesNotIgnoreChanges() { 154 | let newValue = "/newpath/" 155 | 156 | manager.engine = engine 157 | manager.setTestStatus(.connected) 158 | engine.setConnected(true) 159 | manager.config.insert(.path(newValue)) 160 | 161 | XCTAssertEqual(1, manager.config.count) 162 | XCTAssertEqual(manager.engine!.socketPath, newValue) 163 | } 164 | 165 | func testSettingCompressAfterConnectDoesNotIgnoreChanges() { 166 | manager.engine = engine 167 | manager.setTestStatus(.connected) 168 | engine.setConnected(true) 169 | manager.config.insert(.compress) 170 | 171 | XCTAssertEqual(2, manager.config.count) 172 | XCTAssertTrue(manager.engine!.compress) 173 | } 174 | 175 | func testSettingForcePollingAfterConnectDoesNotIgnoreChanges() { 176 | manager.engine = engine 177 | manager.setTestStatus(.connected) 178 | engine.setConnected(true) 179 | manager.config.insert(.forcePolling(true)) 180 | 181 | XCTAssertEqual(2, manager.config.count) 182 | XCTAssertTrue(manager.engine!.forcePolling) 183 | } 184 | 185 | func testSettingForceWebSocketsAfterConnectDoesNotIgnoreChanges() { 186 | manager.engine = engine 187 | manager.setTestStatus(.connected) 188 | engine.setConnected(true) 189 | manager.config.insert(.forceWebsockets(true)) 190 | 191 | XCTAssertEqual(2, manager.config.count) 192 | XCTAssertTrue(manager.engine!.forceWebsockets) 193 | } 194 | 195 | func testChangingEngineHeadersAfterInit() { 196 | engine.extraHeaders = ["Hello": "World"] 197 | 198 | let req = engine.createRequestForPostWithPostWait() 199 | 200 | XCTAssertEqual("World", req.allHTTPHeaderFields?["Hello"]) 201 | } 202 | 203 | var manager: SocketManager! 204 | var socket: SocketIOClient! 205 | var engine: SocketEngine! 206 | 207 | override func setUp() { 208 | super.setUp() 209 | 210 | manager = SocketManager(socketURL: URL(string: "http://localhost")!) 211 | socket = manager.defaultSocket 212 | engine = SocketEngine(client: manager, url: URL(string: "http://localhost")!, options: nil) 213 | 214 | socket.setTestable() 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketIOClientConfigurationTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSocketIOClientConfiguration.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 8/13/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | import SocketIO 11 | 12 | class TestSocketIOClientConfiguration : XCTestCase { 13 | func testReplaceSameOption() { 14 | config.insert(.log(true)) 15 | 16 | XCTAssertEqual(config.count, 2) 17 | 18 | switch config[0] { 19 | case let .log(log): 20 | XCTAssertTrue(log) 21 | default: 22 | XCTFail() 23 | } 24 | } 25 | 26 | func testIgnoreIfExisting() { 27 | config.insert(.forceNew(false), replacing: false) 28 | 29 | XCTAssertEqual(config.count, 2) 30 | 31 | switch config[1] { 32 | case let .forceNew(new): 33 | XCTAssertTrue(new) 34 | default: 35 | XCTFail() 36 | } 37 | } 38 | 39 | var config = [] as SocketIOClientConfiguration 40 | 41 | override func setUp() { 42 | config = [.log(false), .forceNew(true)] 43 | 44 | super.setUp() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketMangerTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Erik Little on 10/21/17. 3 | // 4 | 5 | import Dispatch 6 | import Foundation 7 | @testable import SocketIO 8 | import XCTest 9 | 10 | class SocketMangerTest : XCTestCase { 11 | func testManagerProperties() { 12 | XCTAssertNotNil(manager.defaultSocket) 13 | XCTAssertNil(manager.engine) 14 | XCTAssertFalse(manager.forceNew) 15 | XCTAssertEqual(manager.handleQueue, DispatchQueue.main) 16 | XCTAssertTrue(manager.reconnects) 17 | XCTAssertEqual(manager.reconnectWait, 10) 18 | XCTAssertEqual(manager.status, .notConnected) 19 | } 20 | 21 | func testSettingConfig() { 22 | let manager = SocketManager(socketURL: URL(string: "https://example.com/")!) 23 | 24 | XCTAssertEqual(manager.config.first!, .secure(true)) 25 | 26 | manager.config = [] 27 | 28 | XCTAssertEqual(manager.config.first!, .secure(true)) 29 | } 30 | 31 | func testManagerCallsConnect() { 32 | setUpSockets() 33 | 34 | socket.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the default socket") 35 | socket2.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the socket") 36 | 37 | socket.connect() 38 | socket2.connect() 39 | 40 | manager.fakeConnecting() 41 | manager.fakeConnecting(toNamespace: "/swift") 42 | 43 | waitForExpectations(timeout: 0.3) 44 | } 45 | 46 | func testManagerCallsDisconnect() { 47 | setUpSockets() 48 | 49 | socket.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the default socket") 50 | socket2.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the socket") 51 | 52 | socket2.on(clientEvent: .connect) {data, ack in 53 | self.manager.disconnect() 54 | self.manager.fakeDisconnecting() 55 | } 56 | 57 | socket.connect() 58 | socket2.connect() 59 | 60 | manager.fakeConnecting() 61 | manager.fakeConnecting(toNamespace: "/swift") 62 | 63 | waitForExpectations(timeout: 0.3) 64 | } 65 | 66 | func testManagerEmitAll() { 67 | setUpSockets() 68 | 69 | socket.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the default socket") 70 | socket2.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the socket") 71 | 72 | socket2.on(clientEvent: .connect) {data, ack in 73 | self.manager.emitAll("event", "testing") 74 | } 75 | 76 | socket.connect() 77 | socket2.connect() 78 | 79 | manager.fakeConnecting() 80 | manager.fakeConnecting(toNamespace: "/swift") 81 | 82 | waitForExpectations(timeout: 0.3) 83 | } 84 | 85 | func testManagerSetsConfigs() { 86 | let queue = DispatchQueue(label: "testQueue") 87 | 88 | manager = TestManager(socketURL: URL(string: "http://localhost/")!, config: [ 89 | .handleQueue(queue), 90 | .forceNew(true), 91 | .reconnects(false), 92 | .reconnectWait(5), 93 | .reconnectAttempts(5) 94 | ]) 95 | 96 | XCTAssertEqual(manager.handleQueue, queue) 97 | XCTAssertTrue(manager.forceNew) 98 | XCTAssertFalse(manager.reconnects) 99 | XCTAssertEqual(manager.reconnectWait, 5) 100 | XCTAssertEqual(manager.reconnectAttempts, 5) 101 | } 102 | 103 | func testManagerRemovesSocket() { 104 | setUpSockets() 105 | 106 | manager.removeSocket(socket) 107 | 108 | XCTAssertNil(manager.nsps[socket.nsp]) 109 | } 110 | 111 | private func setUpSockets() { 112 | socket = manager.testSocket(forNamespace: "/") 113 | socket2 = manager.testSocket(forNamespace: "/swift") 114 | } 115 | 116 | private var manager: TestManager! 117 | private var socket: TestSocket! 118 | private var socket2: TestSocket! 119 | 120 | override func setUp() { 121 | super.setUp() 122 | 123 | manager = TestManager(socketURL: URL(string: "http://localhost/")!, config: [.log(false)]) 124 | socket = nil 125 | socket2 = nil 126 | } 127 | } 128 | 129 | public enum ManagerExpectation : String { 130 | case didConnectCalled 131 | case didDisconnectCalled 132 | case emitAllEventCalled 133 | } 134 | 135 | public class TestManager : SocketManager { 136 | public override func disconnect() { 137 | setTestStatus(.disconnected) 138 | } 139 | 140 | @objc 141 | public func testSocket(forNamespace nsp: String) -> TestSocket { 142 | return socket(forNamespace: nsp) as! TestSocket 143 | } 144 | 145 | @objc 146 | public func fakeConnecting(toNamespace nsp: String) { 147 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { 148 | // Fake connecting 149 | self.parseEngineMessage("0\(nsp)") 150 | } 151 | } 152 | 153 | @objc 154 | public func fakeDisconnecting() { 155 | engineDidClose(reason: "") 156 | } 157 | 158 | @objc 159 | public func fakeConnecting() { 160 | engineDidOpen(reason: "") 161 | } 162 | 163 | public override func socket(forNamespace nsp: String) -> SocketIOClient { 164 | // set socket to our test socket, the superclass method will get this from nsps 165 | nsps[nsp] = TestSocket(manager: self, nsp: nsp) 166 | 167 | return super.socket(forNamespace: nsp) 168 | } 169 | } 170 | 171 | public class TestSocket : SocketIOClient { 172 | public var expectations = [ManagerExpectation: XCTestExpectation]() 173 | 174 | @objc 175 | public var expects = NSMutableDictionary() 176 | 177 | public override func didConnect(toNamespace nsp: String) { 178 | expectations[ManagerExpectation.didConnectCalled]?.fulfill() 179 | expectations[ManagerExpectation.didConnectCalled] = nil 180 | 181 | if let expect = expects[ManagerExpectation.didConnectCalled.rawValue] as? XCTestExpectation { 182 | expect.fulfill() 183 | expects[ManagerExpectation.didConnectCalled.rawValue] = nil 184 | } 185 | 186 | super.didConnect(toNamespace: nsp) 187 | } 188 | 189 | public override func didDisconnect(reason: String) { 190 | expectations[ManagerExpectation.didDisconnectCalled]?.fulfill() 191 | expectations[ManagerExpectation.didDisconnectCalled] = nil 192 | 193 | if let expect = expects[ManagerExpectation.didDisconnectCalled.rawValue] as? XCTestExpectation { 194 | expect.fulfill() 195 | expects[ManagerExpectation.didDisconnectCalled.rawValue] = nil 196 | } 197 | 198 | super.didDisconnect(reason: reason) 199 | } 200 | 201 | public override func emit(_ event: String, with items: [Any]) { 202 | expectations[ManagerExpectation.emitAllEventCalled]?.fulfill() 203 | expectations[ManagerExpectation.emitAllEventCalled] = nil 204 | 205 | if let expect = expects[ManagerExpectation.emitAllEventCalled.rawValue] as? XCTestExpectation { 206 | expect.fulfill() 207 | expects[ManagerExpectation.emitAllEventCalled.rawValue] = nil 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketNamespacePacketTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketNamespacePacketTest.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 10/11/15. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import SocketIO 11 | 12 | class SocketNamespacePacketTest : XCTestCase { 13 | func testEmptyEmit() { 14 | let sendData: [Any] = ["test"] 15 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 16 | let parsed = parser.parseSocketMessage(packet.packetString)! 17 | 18 | XCTAssertEqual(parsed.type, .event) 19 | XCTAssertEqual(parsed.nsp, "/swift") 20 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 21 | } 22 | 23 | func testNullEmit() { 24 | let sendData: [Any] = ["test", NSNull()] 25 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 26 | let parsed = parser.parseSocketMessage(packet.packetString)! 27 | 28 | XCTAssertEqual(parsed.type, .event) 29 | XCTAssertEqual(parsed.nsp, "/swift") 30 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 31 | } 32 | 33 | func testStringEmit() { 34 | let sendData: [Any] = ["test", "foo bar"] 35 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 36 | let parsed = parser.parseSocketMessage(packet.packetString)! 37 | 38 | XCTAssertEqual(parsed.type, .event) 39 | XCTAssertEqual(parsed.nsp, "/swift") 40 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 41 | } 42 | 43 | func testJSONEmit() { 44 | let sendData: [Any] = ["test", ["foobar": true, "hello": 1, "test": "hello", "null": NSNull()] as NSDictionary] 45 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 46 | let parsed = parser.parseSocketMessage(packet.packetString)! 47 | 48 | XCTAssertEqual(parsed.type, .event) 49 | XCTAssertEqual(parsed.nsp, "/swift") 50 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 51 | } 52 | 53 | func testArrayEmit() { 54 | let sendData: [Any] = ["test", ["hello", 1, ["test": "test"], true]] 55 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 56 | let parsed = parser.parseSocketMessage(packet.packetString)! 57 | 58 | XCTAssertEqual(parsed.type, .event) 59 | XCTAssertEqual(parsed.nsp, "/swift") 60 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 61 | } 62 | 63 | func testBinaryEmit() { 64 | let sendData: [Any] = ["test", data] 65 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 66 | let parsed = parser.parseSocketMessage(packet.packetString)! 67 | 68 | XCTAssertEqual(parsed.type, .binaryEvent) 69 | XCTAssertEqual(parsed.nsp, "/swift") 70 | XCTAssertEqual(packet.binary, [data]) 71 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 72 | "test", 73 | ["_placeholder": true, "num": 0] 74 | ])) 75 | } 76 | 77 | func testMultipleBinaryEmit() { 78 | let sendData: [Any] = ["test", ["data1": data, "data2": data2] as NSDictionary] 79 | let packet = SocketPacket.packetFromEmit(sendData, id: -1, nsp: "/swift", ack: false) 80 | let parsed = parser.parseSocketMessage(packet.packetString)! 81 | 82 | XCTAssertEqual(parsed.type, .binaryEvent) 83 | XCTAssertEqual(parsed.nsp, "/swift") 84 | 85 | let binaryObj = parsed.data[1] as! [String: Any] 86 | let data1Loc = (binaryObj["data1"] as! [String: Any])["num"] as! Int 87 | let data2Loc = (binaryObj["data2"] as! [String: Any])["num"] as! Int 88 | 89 | XCTAssertEqual(packet.binary[data1Loc], data) 90 | XCTAssertEqual(packet.binary[data2Loc], data2) 91 | } 92 | 93 | func testEmitWithAck() { 94 | let sendData = ["test"] 95 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: false) 96 | let parsed = parser.parseSocketMessage(packet.packetString)! 97 | 98 | XCTAssertEqual(parsed.type, .event) 99 | XCTAssertEqual(parsed.nsp, "/swift") 100 | XCTAssertEqual(parsed.id, 0) 101 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 102 | } 103 | 104 | func testEmitDataWithAck() { 105 | let sendData: [Any] = ["test", data] 106 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: false) 107 | let parsed = parser.parseSocketMessage(packet.packetString)! 108 | 109 | XCTAssertEqual(parsed.type, .binaryEvent) 110 | XCTAssertEqual(parsed.nsp, "/swift") 111 | XCTAssertEqual(parsed.id, 0) 112 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 113 | "test", 114 | ["_placeholder": true, "num": 0] 115 | ])) 116 | } 117 | 118 | // Acks 119 | func testEmptyAck() { 120 | let packet = SocketPacket.packetFromEmit([], id: 0, nsp: "/swift", ack: true) 121 | let parsed = parser.parseSocketMessage(packet.packetString)! 122 | 123 | XCTAssertEqual(parsed.type, .ack) 124 | XCTAssertEqual(parsed.nsp, "/swift") 125 | XCTAssertEqual(parsed.id, 0) 126 | } 127 | 128 | func testNullAck() { 129 | let sendData = [NSNull()] 130 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: true) 131 | let parsed = parser.parseSocketMessage(packet.packetString)! 132 | 133 | XCTAssertEqual(parsed.type, .ack) 134 | XCTAssertEqual(parsed.nsp, "/swift") 135 | XCTAssertEqual(parsed.id, 0) 136 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 137 | } 138 | 139 | func testStringAck() { 140 | let sendData = ["test"] 141 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: true) 142 | let parsed = parser.parseSocketMessage(packet.packetString)! 143 | 144 | XCTAssertEqual(parsed.type, .ack) 145 | XCTAssertEqual(parsed.nsp, "/swift") 146 | XCTAssertEqual(parsed.id, 0) 147 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 148 | } 149 | 150 | func testJSONAck() { 151 | let sendData = [["foobar": true, "hello": 1, "test": "hello", "null": NSNull()]] 152 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: true) 153 | let parsed = parser.parseSocketMessage(packet.packetString)! 154 | 155 | XCTAssertEqual(parsed.type, .ack) 156 | XCTAssertEqual(parsed.nsp, "/swift") 157 | XCTAssertEqual(parsed.id, 0) 158 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: sendData)) 159 | } 160 | 161 | func testBinaryAck() { 162 | let sendData = [data] 163 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: true) 164 | let parsed = parser.parseSocketMessage(packet.packetString)! 165 | 166 | XCTAssertEqual(parsed.type, .binaryAck) 167 | XCTAssertEqual(parsed.nsp, "/swift") 168 | XCTAssertEqual(parsed.id, 0) 169 | XCTAssertTrue(compareAnyArray(input: parsed.data, expected: [ 170 | ["_placeholder": true, "num": 0] 171 | ])) 172 | } 173 | 174 | func testMultipleBinaryAck() { 175 | let sendData = [["data1": data, "data2": data2]] 176 | let packet = SocketPacket.packetFromEmit(sendData, id: 0, nsp: "/swift", ack: true) 177 | let parsed = parser.parseSocketMessage(packet.packetString)! 178 | 179 | XCTAssertEqual(parsed.type, .binaryAck) 180 | XCTAssertEqual(parsed.nsp, "/swift") 181 | XCTAssertEqual(parsed.id, 0) 182 | 183 | let binaryObj = parsed.data[0] as! [String: Any] 184 | let data1Loc = (binaryObj["data1"] as! [String: Any])["num"] as! Int 185 | let data2Loc = (binaryObj["data2"] as! [String: Any])["num"] as! Int 186 | 187 | XCTAssertEqual(packet.binary[data1Loc], data) 188 | XCTAssertEqual(packet.binary[data2Loc], data2) 189 | } 190 | 191 | let data = "test".data(using: String.Encoding.utf8)! 192 | let data2 = "test2".data(using: String.Encoding.utf8)! 193 | var parser: SocketParsable! 194 | 195 | private func compareAnyArray(input: [Any], expected: [Any]) -> Bool { 196 | guard input.count == expected.count else { return false } 197 | 198 | return (input as NSArray).isEqual(to: expected) 199 | } 200 | 201 | override func setUp() { 202 | super.setUp() 203 | 204 | parser = SocketManager(socketURL: URL(string: "http://localhost")!) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Tests/TestSocketIO/SocketParserTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SocketParserTest.swift 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Lukas Schmidt on 05.09.15. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import SocketIO 11 | 12 | class SocketParserTest: XCTestCase { 13 | func testDisconnect() { 14 | let message = "1" 15 | validateParseResult(message) 16 | } 17 | 18 | func testConnect() { 19 | let message = "0" 20 | validateParseResult(message) 21 | } 22 | 23 | func testDisconnectNameSpace() { 24 | let message = "1/swift" 25 | validateParseResult(message) 26 | } 27 | 28 | func testConnecttNameSpace() { 29 | let message = "0/swift" 30 | validateParseResult(message) 31 | } 32 | 33 | func testIdEvent() { 34 | let message = "25[\"test\"]" 35 | validateParseResult(message) 36 | } 37 | 38 | func testBinaryPlaceholderAsString() { 39 | let message = "2[\"test\",\"~~0\"]" 40 | validateParseResult(message) 41 | } 42 | 43 | func testNameSpaceArrayParse() { 44 | let message = "2/swift,[\"testArrayEmitReturn\",[\"test3\",\"test4\"]]" 45 | validateParseResult(message) 46 | } 47 | 48 | func testNameSpaceArrayAckParse() { 49 | let message = "3/swift,0[[\"test3\",\"test4\"]]" 50 | validateParseResult(message) 51 | } 52 | 53 | func testNameSpaceBinaryEventParse() { 54 | let message = "51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]" 55 | validateParseResult(message) 56 | } 57 | 58 | func testNameSpaceBinaryAckParse() { 59 | let message = "61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]" 60 | validateParseResult(message) 61 | } 62 | 63 | func testNamespaceErrorParse() { 64 | let message = "4/swift," 65 | validateParseResult(message) 66 | } 67 | 68 | func testErrorTypeString() { 69 | let message = "4\"ERROR\"" 70 | validateParseResult(message) 71 | } 72 | 73 | func testErrorTypeDictionary() { 74 | let message = "4{\"test\":2}" 75 | validateParseResult(message) 76 | } 77 | 78 | func testErrorTypeInt() { 79 | let message = "41" 80 | validateParseResult(message) 81 | } 82 | 83 | func testErrorTypeArray() { 84 | let message = "4[1, \"hello\"]" 85 | validateParseResult(message) 86 | } 87 | 88 | func testInvalidInput() { 89 | let message = "8" 90 | do { 91 | let _ = try testManager.parseString(message) 92 | XCTFail() 93 | } catch { 94 | 95 | } 96 | } 97 | 98 | func testGenericParser() { 99 | var parser = SocketStringReader(message: "61-/swift,") 100 | XCTAssertEqual(parser.read(count: 1), "6") 101 | XCTAssertEqual(parser.currentCharacter, "1") 102 | XCTAssertEqual(parser.readUntilOccurence(of: "-"), "1") 103 | XCTAssertEqual(parser.currentCharacter, "/") 104 | } 105 | 106 | func validateParseResult(_ message: String) { 107 | let validValues = SocketParserTest.packetTypes[message]! 108 | let packet = try! testManager.parseString(message) 109 | let type = String(message.prefix(1)) 110 | 111 | XCTAssertEqual(packet.type, SocketPacket.PacketType(rawValue: Int(type) ?? -1)!) 112 | XCTAssertEqual(packet.nsp, validValues.0) 113 | XCTAssertTrue((packet.data as NSArray).isEqual(to: validValues.1), "\(packet.data)") 114 | XCTAssertTrue((packet.binary as NSArray).isEqual(to: validValues.2), "\(packet.binary)") 115 | XCTAssertEqual(packet.id, validValues.3) 116 | } 117 | 118 | func testParsePerformance() { 119 | let keys = Array(SocketParserTest.packetTypes.keys) 120 | measure { 121 | for item in keys.enumerated() { 122 | _ = try! self.testManager.parseString(item.element) 123 | } 124 | } 125 | } 126 | 127 | let testManager = SocketManager(socketURL: URL(string: "http://localhost/")!) 128 | 129 | //Format key: message; namespace-data-binary-id 130 | static let packetTypes: [String: (String, [Any], [Data], Int)] = [ 131 | "0": ("/", [], [], -1), "1": ("/", [], [], -1), 132 | "25[\"test\"]": ("/", ["test"], [], 5), 133 | "2[\"test\",\"~~0\"]": ("/", ["test", "~~0"], [], -1), 134 | "2/swift,[\"testArrayEmitReturn\",[\"test3\",\"test4\"]]": ("/swift", ["testArrayEmitReturn", ["test3", "test4"] as NSArray], [], -1), 135 | "51-/swift,[\"testMultipleItemsWithBufferEmitReturn\",[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": ("/swift", ["testMultipleItemsWithBufferEmitReturn", [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], -1), 136 | "3/swift,0[[\"test3\",\"test4\"]]": ("/swift", [["test3", "test4"] as NSArray], [], 0), 137 | "61-/swift,19[[1,2],{\"test\":\"bob\"},25,\"polo\",{\"_placeholder\":true,\"num\":0}]": 138 | ("/swift", [ [1, 2] as NSArray, ["test": "bob"] as NSDictionary, 25, "polo", ["_placeholder": true, "num": 0] as NSDictionary], [], 19), 139 | "4/swift,": ("/swift", [], [], -1), 140 | "0/swift": ("/swift", [], [], -1), 141 | "1/swift": ("/swift", [], [], -1), 142 | "4\"ERROR\"": ("/", ["ERROR"], [], -1), 143 | "4{\"test\":2}": ("/", [["test": 2]], [], -1), 144 | "41": ("/", [1], [], -1), 145 | "4[1, \"hello\"]": ("/", [1, "hello"], [], -1) 146 | ] 147 | } 148 | -------------------------------------------------------------------------------- /Tests/TestSocketIOObjc/ManagerObjectiveCTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Erik Little on 10/21/17. 3 | // 4 | 5 | #import "SocketIO_Tests-Swift.h" 6 | 7 | @import XCTest; 8 | @import SocketIO; 9 | 10 | @interface ManagerObjectiveCTest : XCTestCase 11 | 12 | @property TestSocket* socket; 13 | @property TestSocket* socket2; 14 | @property TestManager* manager; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/TestSocketIOObjc/ManagerObjectiveCTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Erik Little on 10/21/17. 3 | // 4 | 5 | #import "ManagerObjectiveCTest.h" 6 | 7 | @import Dispatch; 8 | @import Foundation; 9 | @import XCTest; 10 | @import SocketIO; 11 | 12 | @implementation ManagerObjectiveCTest 13 | 14 | - (void)testSettingConfig { 15 | NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"]; 16 | NSDictionary* headers = @{@"My Header": @"Some Value"}; 17 | 18 | self.manager = [[TestManager alloc] initWithSocketURL:url config:@{ 19 | @"forceNew": @YES, 20 | @"extraHeaders": headers 21 | }]; 22 | 23 | [self.manager connect]; 24 | 25 | XCTAssertTrue(self.manager.forceNew); 26 | XCTAssertTrue([self.manager.engine.extraHeaders isEqualToDictionary:headers]); 27 | 28 | } 29 | 30 | - (void)testManagerProperties { 31 | XCTAssertNotNil(self.manager.defaultSocket); 32 | XCTAssertNil(self.manager.engine); 33 | XCTAssertFalse(self.manager.forceNew); 34 | XCTAssertEqual(self.manager.handleQueue, dispatch_get_main_queue()); 35 | XCTAssertTrue(self.manager.reconnects); 36 | XCTAssertEqual(self.manager.reconnectWait, 10); 37 | XCTAssertEqual(self.manager.status, SocketIOStatusNotConnected); 38 | } 39 | 40 | - (void)testConnectSocketSyntax { 41 | [self setUpSockets]; 42 | [self.manager connectSocket:self.socket]; 43 | } 44 | 45 | - (void)testDisconnectSocketSyntax { 46 | [self setUpSockets]; 47 | [self.manager disconnectSocket:self.socket]; 48 | } 49 | 50 | - (void)testSocketForNamespaceSyntax { 51 | SocketIOClient* client = [self.manager socketForNamespace:@"/swift"]; 52 | client = nil; 53 | } 54 | 55 | - (void)testManagerCallsConnect { 56 | [self setUpSockets]; 57 | 58 | XCTestExpectation* expect = [self expectationWithDescription:@"The manager should call connect on the default socket"]; 59 | XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should call connect on the socket"]; 60 | 61 | self.socket.expects[@"didConnectCalled"] = expect; 62 | self.socket2.expects[@"didConnectCalled"] = expect2; 63 | 64 | [self.socket connect]; 65 | [self.socket2 connect]; 66 | 67 | [self.manager fakeConnecting]; 68 | [self.manager fakeConnectingToNamespace:@"/swift"]; 69 | 70 | [self waitForExpectationsWithTimeout:0.3 handler:nil]; 71 | } 72 | 73 | - (void)testManagerCallsDisconnect { 74 | [self setUpSockets]; 75 | 76 | XCTestExpectation* expect = [self expectationWithDescription:@"The manager should call disconnect on the default socket"]; 77 | XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should call disconnect on the socket"]; 78 | 79 | self.socket.expects[@"didDisconnectCalled"] = expect; 80 | self.socket2.expects[@"didDisconnectCalled"] = expect2; 81 | 82 | [self.socket2 on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) { 83 | [self.manager disconnect]; 84 | [self.manager fakeDisconnecting]; 85 | }]; 86 | 87 | [self.socket connect]; 88 | [self.socket2 connect]; 89 | 90 | [self.manager fakeConnecting]; 91 | [self.manager fakeConnectingToNamespace:@"/swift"]; 92 | 93 | [self waitForExpectationsWithTimeout:0.3 handler:nil]; 94 | } 95 | 96 | - (void)testManagerEmitAll { 97 | [self setUpSockets]; 98 | 99 | XCTestExpectation* expect = [self expectationWithDescription:@"The manager should emit an event to the default socket"]; 100 | XCTestExpectation* expect2 = [self expectationWithDescription:@"The manager should emit an event to the socket"]; 101 | 102 | self.socket.expects[@"emitAllEventCalled"] = expect; 103 | self.socket2.expects[@"emitAllEventCalled"] = expect2; 104 | 105 | [self.socket2 on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) { 106 | [self.manager emitAll:@"event" withItems:@[@"testing"]]; 107 | }]; 108 | 109 | [self.socket connect]; 110 | [self.socket2 connect]; 111 | 112 | [self.manager fakeConnecting]; 113 | [self.manager fakeConnectingToNamespace:@"/swift"]; 114 | 115 | [self waitForExpectationsWithTimeout:0.3 handler:nil]; 116 | } 117 | 118 | - (void)testMangerRemoveSocket { 119 | [self setUpSockets]; 120 | 121 | [self.manager removeSocket:self.socket]; 122 | 123 | XCTAssertNil(self.manager.nsps[self.socket.nsp]); 124 | } 125 | 126 | - (void)setUpSockets { 127 | self.socket = [self.manager testSocketForNamespace:@"/"]; 128 | self.socket2 = [self.manager testSocketForNamespace:@"/swift"]; 129 | } 130 | 131 | - (void)setUp { 132 | [super setUp]; 133 | NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"]; 134 | self.manager = [[TestManager alloc] initWithSocketURL:url config:@{@"log": @NO}]; 135 | self.socket = nil; 136 | self.socket2 = nil; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /Tests/TestSocketIOObjc/SocketObjectiveCTest.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Erik Little on 10/21/17. 3 | // 4 | 5 | 6 | @import Dispatch; 7 | @import Foundation; 8 | @import XCTest; 9 | @import SocketIO; 10 | 11 | @interface SocketObjectiveCTest : XCTestCase 12 | 13 | @property SocketIOClient* socket; 14 | @property SocketManager* manager; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Tests/TestSocketIOObjc/SocketObjectiveCTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // SocketObjectiveCTest.m 3 | // Socket.IO-Client-Swift 4 | // 5 | // Created by Erik Little on 3/25/16. 6 | // 7 | // Merely tests whether the Objective-C api breaks 8 | // 9 | 10 | #import "SocketObjectiveCTest.h" 11 | 12 | @import Dispatch; 13 | @import Foundation; 14 | @import XCTest; 15 | @import SocketIO; 16 | 17 | // TODO Manager interface tests 18 | 19 | @implementation SocketObjectiveCTest 20 | 21 | - (void)testProperties { 22 | XCTAssertTrue([self.socket.nsp isEqualToString:@"/"]); 23 | XCTAssertEqual(self.socket.status, SocketIOStatusNotConnected); 24 | } 25 | 26 | - (void)testOnSyntax { 27 | [self.socket on:@"someCallback" callback:^(NSArray* data, SocketAckEmitter* ack) { 28 | [ack with:@[@1]]; 29 | [[ack rawEmitView] with:@[@"hello"]]; 30 | }]; 31 | } 32 | 33 | - (void)testConnectSyntax { 34 | [self.socket connect]; 35 | } 36 | 37 | - (void)testConnectTimeoutAfterSyntax { 38 | [self.socket connectWithTimeoutAfter:1 withHandler: ^() { }]; 39 | } 40 | 41 | - (void)testDisconnectSyntax { 42 | [self.socket disconnect]; 43 | } 44 | 45 | - (void)testLeaveNamespaceSyntax { 46 | [self.socket leaveNamespace]; 47 | } 48 | 49 | - (void)testJoinNamespaceSyntax { 50 | [self.socket joinNamespace]; 51 | } 52 | 53 | - (void)testOnAnySyntax { 54 | [self.socket onAny:^(SocketAnyEvent* any) { 55 | NSString* event = any.event; 56 | NSArray* data = any.items; 57 | 58 | [self.socket emit:event with:data]; 59 | }]; 60 | } 61 | 62 | - (void)testRemoveAllHandlersSyntax { 63 | [self.socket removeAllHandlers]; 64 | } 65 | 66 | - (void)testEmitSyntax { 67 | [self.socket emit:@"testEmit" with:@[@YES]]; 68 | } 69 | 70 | - (void)testRawEmitSyntax { 71 | [[self.socket rawEmitView] emit:@"myEvent" with:@[@1]]; 72 | } 73 | 74 | - (void)testEmitWithAckSyntax { 75 | [[self.socket emitWithAck:@"testAckEmit" with:@[@YES]] timingOutAfter:0 callback:^(NSArray* data) { }]; 76 | } 77 | 78 | - (void)testOffSyntax { 79 | [self.socket off:@"test"]; 80 | } 81 | 82 | - (void)testSSLSecurity { 83 | SSLSecurity* sec = [[SSLSecurity alloc] initWithUsePublicKeys:0]; 84 | sec = nil; 85 | } 86 | 87 | - (void)setUp { 88 | [super setUp]; 89 | NSURL* url = [[NSURL alloc] initWithString:@"http://localhost"]; 90 | self.manager = [[SocketManager alloc] initWithSocketURL:url config:@{@"log": @NO}]; 91 | self.socket = [self.manager defaultSocket]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /Usage Docs/12to13.md: -------------------------------------------------------------------------------- 1 | # Upgrading from v12 2 | 3 | This guide will help you navigate the changes that were introduced in v13. 4 | 5 | ## What are the big changes? 6 | 7 | The biggest change is how to create and manage clients. Much like the native JS client and server, 8 | the swift client now only uses one engine per connection. Previously in order to use namespaces it was required 9 | to create multiple clients, and each client had its own engine. 10 | 11 | Some v12 code might've looked like this: 12 | 13 | ```swift 14 | let defaultSocket = SocketIOClient(socketURL: myURL) 15 | let namespaceSocket = SocketIOClient(socketURL: myURL, config: [.nsp("/swift")]) 16 | 17 | // add handlers for sockets and connect 18 | 19 | ``` 20 | 21 | In v12 this would have opened two connections to the socket.io. 22 | 23 | 24 | In v13 the same code would look like this: 25 | 26 | ```swift 27 | let manager = SocketManager(socketURL: myURL) 28 | let defaultSocket = manager.defaultSocket 29 | let namespaceSocket = manager.socket(forNamespace: "/swift") 30 | 31 | // add handlers for sockets and connect 32 | ``` 33 | 34 | In v13 `defaultSocket` and `namespaceSocket` will share a single transport. This means one less connection to the server 35 | needs to be opened. 36 | 37 | ## What might I have to change? 38 | 39 | - The most obvious thing you will need to change is that instead of creating `SocketIOClient`s directly, you will create a 40 | `SocketManager` and either use the `defaultSocket` property if you don't need namespaces, or call the 41 | `socket(forNamespace:)` method on the manager. 42 | 43 | - `SocketIOClient` is no longer a client to an engine. So if you were overriding the engine methods, these have been moved 44 | to the manager. 45 | 46 | - The library is now a single target. So you might have to change some of your Xcode project settings. 47 | 48 | - `SocketIOClient`s no longer take a configuration, they are shared from the manager. 49 | 50 | - The `joinNamespace()` and `leaveNamespace()` methods on `SocketIOClient` no longer take any arguments, and in most cases 51 | no longer need to be called. Namespace joining/leaving can be managed by calling `connect()`/`disconnect()` on the socket 52 | associated with that namespace. 53 | 54 | ---------- 55 | 56 | # What things should I know? 57 | 58 | How sockets are stored 59 | --- 60 | 61 | You should know that `SocketIOClient`s no longer need to be held around in properties, but the `SocketManager` should. 62 | 63 | One of the most common mistakes people made is not maintaining a strong reference to the client. 64 | 65 | ```swift 66 | class Manager { 67 | func addHandlers() { 68 | let socket = SocketIOClient(socketURL: myURL, config: [.nsp("/swift")]) 69 | 70 | // Add handlers 71 | } 72 | } 73 | ``` 74 | 75 | This would have resulted in the client being released and no handlers being called. 76 | 77 | A *correct* equivalent would be: 78 | 79 | ```swift 80 | class Manager { 81 | let socketManager = SocketManager(socketURL: someURL) 82 | 83 | func addHandlers() { 84 | let socket = socketManager.socket(forNamespace: "/swift") 85 | 86 | // Add handlers 87 | } 88 | } 89 | ``` 90 | 91 | This code is fine because the `SocketManager` will maintain a strong reference to the socket. 92 | 93 | It's also worth noting that subsequent calls to `socket(forNamespace:)` will return the *same* socket instance as the 94 | first call. So you don't need to hold onto the socket directly just to access it again, just call `socket(forNamespace:)` 95 | on the manager to get it. **This does mean that if you need multiple sockets on the same namespace, you will have to use 96 | multiple managers.** 97 | 98 | What to call connect on 99 | --- 100 | 101 | Connect can either be called on the manager directly, or on one of the sockets made from it. In either case, if the manager 102 | was not already connected to the server, a connection will be made. Also in both cases the default socket (namespace "/") 103 | will fire a `connect` event. 104 | 105 | The difference is that if `connect()` is just called on the manager, then any sockets for that manager that are not the default 106 | socket will not automatically connect. `connect()` will need to be called individually for each socket. However, if `connect()` 107 | is called on a client, then in addition to opening the connection if needed, the client will connect to its namespace, 108 | and a `connect` event fired. 109 | 110 | -------------------------------------------------------------------------------- /Usage Docs/FAQ.md: -------------------------------------------------------------------------------- 1 | ## How do I connect to my WebSocket server? 2 | 3 | This library is **NOT** a WebSockets library. This library is only for servers that implement the socket.io protocol, 4 | such as [socket.io](https://socket.io/). If you need a plain WebSockets client check out 5 | [Starscream](https://github.com/daltoniam/Starscream) for Swift and [JetFire](https://github.com/acmacalister/jetfire) 6 | for Objective-C. 7 | 8 | ## Why isn't my event handler being called? 9 | 10 | One of the most common reasons your event might not be called is if the client is released by 11 | [ARC](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html). 12 | 13 | Take this code for example: 14 | 15 | ```swift 16 | class Manager { 17 | func addHandlers() { 18 | let manager = SocketManager(socketURL: URL(string: "http://somesocketioserver.com")!) 19 | 20 | manager.defaultSocket.on("myEvent") {data, ack in 21 | print(data) 22 | } 23 | } 24 | 25 | } 26 | ``` 27 | 28 | This code is **incorrect**, and the event handler will never be called. Because as soon as this method is called `manager` 29 | will be released, along with the socket, and its memory reclaimed. 30 | 31 | A correct way would be: 32 | 33 | ```swift 34 | class Manager { 35 | let manager = SocketManager(socketURL: URL(string: "http://somesocketioserver.com")!) 36 | 37 | func addHandlers() { 38 | manager.defaultSocket.on("myEvent") {data, ack in 39 | print(data) 40 | } 41 | } 42 | } 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/Enums/SocketAckStatus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SocketAckStatus Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | SocketIO Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 | 36 |
37 | 38 | 43 | 44 |
45 | 181 |
182 | 183 |
184 |
185 |

SocketAckStatus

186 |
187 |
188 |
public enum SocketAckStatus : String
189 | 190 |
191 |
192 |

The status of an ack.

193 | 194 |
195 |
196 | 197 |
198 |
199 |
200 |
201 | 202 | 203 | 204 |

Cases

205 |
206 |
207 |
    208 |
  • 209 |
    210 | 211 | 212 | 213 | noAck 214 | 215 |
    216 |
    217 |
    218 |
    219 |
    220 |
    221 |

    The ack timed out.

    222 | 223 |
    224 |
    225 |

    Declaration

    226 |
    227 |

    Swift

    228 |
    case noAck = "NO ACK"
    229 | 230 |
    231 |
    232 |
    233 |
    234 |
  • 235 |
236 |
237 |
238 |
239 | 240 |
241 |
242 | 246 | 247 | 248 | 249 | -------------------------------------------------------------------------------- /docs/Guides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Guides Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | SocketIO Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 | 36 |
37 | 38 | 43 | 44 |
45 | 181 |
182 | 183 |
184 |
185 |

Guides

186 |

The following guides are available globally.

187 | 188 |
189 |
190 | 191 |
192 |
193 |
194 |
195 | 196 |
197 |
198 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/css/jazzy.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: inherit; } 3 | 4 | body { 5 | margin: 0; 6 | background: #fff; 7 | color: #333; 8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | letter-spacing: .2px; 10 | -webkit-font-smoothing: antialiased; 11 | box-sizing: border-box; } 12 | 13 | h1 { 14 | font-size: 2rem; 15 | font-weight: 700; 16 | margin: 1.275em 0 0.6em; } 17 | 18 | h2 { 19 | font-size: 1.75rem; 20 | font-weight: 700; 21 | margin: 1.275em 0 0.3em; } 22 | 23 | h3 { 24 | font-size: 1.5rem; 25 | font-weight: 700; 26 | margin: 1em 0 0.3em; } 27 | 28 | h4 { 29 | font-size: 1.25rem; 30 | font-weight: 700; 31 | margin: 1.275em 0 0.85em; } 32 | 33 | h5 { 34 | font-size: 1rem; 35 | font-weight: 700; 36 | margin: 1.275em 0 0.85em; } 37 | 38 | h6 { 39 | font-size: 1rem; 40 | font-weight: 700; 41 | margin: 1.275em 0 0.85em; 42 | color: #777; } 43 | 44 | p { 45 | margin: 0 0 1em; } 46 | 47 | ul, ol { 48 | padding: 0 0 0 2em; 49 | margin: 0 0 0.85em; } 50 | 51 | blockquote { 52 | margin: 0 0 0.85em; 53 | padding: 0 15px; 54 | color: #858585; 55 | border-left: 4px solid #e5e5e5; } 56 | 57 | img { 58 | max-width: 100%; } 59 | 60 | a { 61 | color: #4183c4; 62 | text-decoration: none; } 63 | a:hover, a:focus { 64 | outline: 0; 65 | text-decoration: underline; } 66 | 67 | table { 68 | background: #fff; 69 | width: 100%; 70 | border-collapse: collapse; 71 | border-spacing: 0; 72 | overflow: auto; 73 | margin: 0 0 0.85em; } 74 | 75 | tr:nth-child(2n) { 76 | background-color: #fbfbfb; } 77 | 78 | th, td { 79 | padding: 6px 13px; 80 | border: 1px solid #ddd; } 81 | 82 | pre { 83 | margin: 0 0 1.275em; 84 | padding: .85em 1em; 85 | overflow: auto; 86 | background: #f7f7f7; 87 | font-size: .85em; 88 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 89 | 90 | code { 91 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 92 | 93 | p > code, li > code { 94 | background: #f7f7f7; 95 | padding: .2em; } 96 | p > code:before, p > code:after, li > code:before, li > code:after { 97 | letter-spacing: -.2em; 98 | content: "\00a0"; } 99 | 100 | pre code { 101 | padding: 0; 102 | white-space: pre; } 103 | 104 | .content-wrapper { 105 | display: flex; 106 | flex-direction: column; } 107 | @media (min-width: 768px) { 108 | .content-wrapper { 109 | flex-direction: row; } } 110 | 111 | .header { 112 | display: flex; 113 | padding: 8px; 114 | font-size: 0.875em; 115 | background: #444; 116 | color: #999; } 117 | 118 | .header-col { 119 | margin: 0; 120 | padding: 0 8px; } 121 | 122 | .header-col--primary { 123 | flex: 1; } 124 | 125 | .header-link { 126 | color: #fff; } 127 | 128 | .header-icon { 129 | padding-right: 6px; 130 | vertical-align: -4px; 131 | height: 16px; } 132 | 133 | .breadcrumbs { 134 | font-size: 0.875em; 135 | padding: 8px 16px; 136 | margin: 0; 137 | background: #fbfbfb; 138 | border-bottom: 1px solid #ddd; } 139 | 140 | .carat { 141 | height: 10px; 142 | margin: 0 5px; } 143 | 144 | .navigation { 145 | order: 2; } 146 | @media (min-width: 768px) { 147 | .navigation { 148 | order: 1; 149 | width: 25%; 150 | max-width: 300px; 151 | padding-bottom: 64px; 152 | overflow: hidden; 153 | word-wrap: normal; 154 | background: #fbfbfb; 155 | border-right: 1px solid #ddd; } } 156 | 157 | .nav-groups { 158 | list-style-type: none; 159 | padding-left: 0; } 160 | 161 | .nav-group-name { 162 | border-bottom: 1px solid #ddd; 163 | padding: 8px 0 8px 16px; } 164 | 165 | .nav-group-name-link { 166 | color: #333; } 167 | 168 | .nav-group-tasks { 169 | margin: 8px 0; 170 | padding: 0 0 0 8px; } 171 | 172 | .nav-group-task { 173 | font-size: 1em; 174 | list-style-type: none; 175 | white-space: nowrap; } 176 | 177 | .nav-group-task-link { 178 | color: #808080; } 179 | 180 | .main-content { 181 | order: 1; } 182 | @media (min-width: 768px) { 183 | .main-content { 184 | order: 2; 185 | flex: 1; 186 | padding-bottom: 60px; } } 187 | 188 | .section { 189 | padding: 0 32px; 190 | border-bottom: 1px solid #ddd; } 191 | 192 | .section-content { 193 | max-width: 834px; 194 | margin: 0 auto; 195 | padding: 16px 0; } 196 | 197 | .section-name { 198 | color: #666; 199 | display: block; } 200 | 201 | .declaration .highlight { 202 | overflow-x: initial; 203 | padding: 8px 0; 204 | margin: 0; 205 | background-color: transparent; 206 | border: none; } 207 | 208 | .task-group-section { 209 | border-top: 1px solid #ddd; } 210 | 211 | .task-group { 212 | padding-top: 0px; } 213 | 214 | .task-name-container a[name]:before { 215 | content: ""; 216 | display: block; } 217 | 218 | .item-container { 219 | padding: 0; } 220 | 221 | .item { 222 | padding-top: 8px; 223 | width: 100%; 224 | list-style-type: none; } 225 | .item a[name]:before { 226 | content: ""; 227 | display: block; } 228 | .item .token { 229 | padding-left: 3px; 230 | margin-left: 0px; 231 | font-size: 1rem; } 232 | .item .declaration-note { 233 | font-size: .85em; 234 | color: #808080; 235 | font-style: italic; } 236 | 237 | .pointer-container { 238 | border-bottom: 1px solid #ddd; 239 | left: -23px; 240 | padding-bottom: 13px; 241 | position: relative; 242 | width: 110%; } 243 | 244 | .pointer { 245 | left: 21px; 246 | top: 7px; 247 | display: block; 248 | position: absolute; 249 | width: 12px; 250 | height: 12px; 251 | border-left: 1px solid #ddd; 252 | border-top: 1px solid #ddd; 253 | background: #fff; 254 | transform: rotate(45deg); } 255 | 256 | .height-container { 257 | display: none; 258 | position: relative; 259 | width: 100%; 260 | overflow: hidden; } 261 | .height-container .section { 262 | background: #fff; 263 | border: 1px solid #ddd; 264 | border-top-width: 0; 265 | padding-top: 10px; 266 | padding-bottom: 5px; 267 | padding: 8px 16px; } 268 | 269 | .aside, .language { 270 | padding: 6px 12px; 271 | margin: 12px 0; 272 | border-left: 5px solid #dddddd; 273 | overflow-y: hidden; } 274 | .aside .aside-title, .language .aside-title { 275 | font-size: 9px; 276 | letter-spacing: 2px; 277 | text-transform: uppercase; 278 | padding-bottom: 0; 279 | margin: 0; 280 | color: #aaa; 281 | -webkit-user-select: none; } 282 | .aside p:last-child, .language p:last-child { 283 | margin-bottom: 0; } 284 | 285 | .language { 286 | border-left: 5px solid #cde9f4; } 287 | .language .aside-title { 288 | color: #4183c4; } 289 | 290 | .aside-warning { 291 | border-left: 5px solid #ff6666; } 292 | .aside-warning .aside-title { 293 | color: #ff0000; } 294 | 295 | .graybox { 296 | border-collapse: collapse; 297 | width: 100%; } 298 | .graybox p { 299 | margin: 0; 300 | word-break: break-word; 301 | min-width: 50px; } 302 | .graybox td { 303 | border: 1px solid #ddd; 304 | padding: 5px 25px 5px 10px; 305 | vertical-align: middle; } 306 | .graybox tr td:first-of-type { 307 | text-align: right; 308 | padding: 7px; 309 | vertical-align: top; 310 | word-break: normal; 311 | width: 40px; } 312 | 313 | .slightly-smaller { 314 | font-size: 0.9em; } 315 | 316 | .footer { 317 | padding: 8px 16px; 318 | background: #444; 319 | color: #ddd; 320 | font-size: 0.8em; } 321 | .footer p { 322 | margin: 8px 0; } 323 | .footer a { 324 | color: #fff; } 325 | 326 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation { 327 | display: none; } 328 | html.dash .height-container { 329 | display: block; } 330 | 331 | form[role=search] input { 332 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 333 | font-size: 14px; 334 | line-height: 24px; 335 | padding: 0 10px; 336 | margin: 0; 337 | border: none; 338 | border-radius: 1em; } 339 | .loading form[role=search] input { 340 | background: white url(../img/spinner.gif) center right 4px no-repeat; } 341 | form[role=search] .tt-menu { 342 | margin: 0; 343 | min-width: 300px; 344 | background: #fbfbfb; 345 | color: #333; 346 | border: 1px solid #ddd; } 347 | form[role=search] .tt-highlight { 348 | font-weight: bold; } 349 | form[role=search] .tt-suggestion { 350 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 351 | padding: 0 8px; } 352 | form[role=search] .tt-suggestion span { 353 | display: table-cell; 354 | white-space: nowrap; } 355 | form[role=search] .tt-suggestion .doc-parent-name { 356 | width: 100%; 357 | text-align: right; 358 | font-weight: normal; 359 | font-size: 0.9em; 360 | padding-left: 16px; } 361 | form[role=search] .tt-suggestion:hover, 362 | form[role=search] .tt-suggestion.tt-cursor { 363 | cursor: pointer; 364 | background-color: #4183c4; 365 | color: #fff; } 366 | form[role=search] .tt-suggestion:hover .doc-parent-name, 367 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { 368 | color: #fff; } 369 | -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahidattique55/socket.io-client-swift/016e12720dd20105b21409341b709cb235066acf/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahidattique55/socket.io-client-swift/016e12720dd20105b21409341b709cb235066acf/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahidattique55/socket.io-client-swift/016e12720dd20105b21409341b709cb235066acf/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fahidattique55/socket.io-client-swift/016e12720dd20105b21409341b709cb235066acf/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | 39 | // Dumb down quotes within code blocks that delimit strings instead of quotations 40 | // https://github.com/realm/jazzy/issues/714 41 | $("code q").replaceWith(function () { 42 | return ["\"", $(this).contents(), "\""]; 43 | }); 44 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var searchIndex = lunr(function() { 3 | this.ref('url'); 4 | this.field('name'); 5 | }); 6 | 7 | var $typeahead = $('[data-typeahead]'); 8 | var $form = $typeahead.parents('form'); 9 | var searchURL = $form.attr('action'); 10 | 11 | function displayTemplate(result) { 12 | return result.name; 13 | } 14 | 15 | function suggestionTemplate(result) { 16 | var t = '
'; 17 | t += '' + result.name + ''; 18 | if (result.parent_name) { 19 | t += '' + result.parent_name + ''; 20 | } 21 | t += '
'; 22 | return t; 23 | } 24 | 25 | $typeahead.one('focus', function() { 26 | $form.addClass('loading'); 27 | 28 | $.getJSON(searchURL).then(function(searchData) { 29 | $.each(searchData, function (url, doc) { 30 | searchIndex.add({url: url, name: doc.name}); 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3 37 | }, 38 | { 39 | limit: 10, 40 | display: displayTemplate, 41 | templates: { suggestion: suggestionTemplate }, 42 | source: function(query, sync) { 43 | var results = searchIndex.search(query).map(function(result) { 44 | var doc = searchData[result.ref]; 45 | doc.url = result.ref; 46 | return doc; 47 | }); 48 | sync(results); 49 | } 50 | } 51 | ); 52 | $form.removeClass('loading'); 53 | $typeahead.trigger('focus'); 54 | }); 55 | }); 56 | 57 | var baseURL = searchURL.slice(0, -"search.json".length); 58 | 59 | $typeahead.on('typeahead:select', function(e, result) { 60 | window.location = baseURL + result.url; 61 | }); 62 | }); 63 | --------------------------------------------------------------------------------