├── .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 | [](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 |
34 |
35 |
36 |
37 |
38 |
39 | SocketIO Reference
40 |
41 | SocketAckStatus Enumeration Reference
42 |
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 |
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 |
37 |
38 |
39 | SocketIO Reference
40 |
41 | Guides Reference
42 |
43 |
44 |
45 |
181 |
182 |
183 |
184 |
185 |
Guides
186 |
The following guides are available globally.
187 |
188 |
189 |
190 |
191 |
195 |
196 |
197 |
198 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/docs/badge.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------