├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── 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
│ ├── 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
│ └── utils.swift
├── Usage Docs
├── 12to13.md
├── 15to16.md
├── Compatibility.md
└── FAQ.md
└── docs
├── 12to13.html
├── 15to16.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
├── SocketIOVersion.html
└── SocketParsableError.html
├── Extensions.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
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on:
4 | push:
5 | branches: [ "master", "development" ]
6 | pull_request:
7 | branches: [ "master", "development" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Build
17 | run: swift build -v
18 | - name: Run tests
19 | run: swift test -v
20 |
--------------------------------------------------------------------------------
/.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 |
54 | .swiftpm
55 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v16.1.0
2 |
3 | - Remove support for iOS 11.
4 | - Update to Starscream 4.0.6
5 |
6 | # v16.0.0
7 |
8 | - Removed Objective-C support. It's time for you to embrace Swift.
9 | - Socket.io 3 support.
10 |
11 | # v15.3.0
12 |
13 | - Add `==` operators for `SocketAckStatus` and `String`
14 |
15 | # v15.2.0
16 |
17 | - Small fixes.
18 |
19 | # v15.1.0
20 |
21 | - Add ability to enable websockets SOCKS proxy.
22 | - Fix emit completion callback not firing on websockets [#1178](https://github.com/socketio/socket.io-client-swift/issues/1178)
23 |
24 | # v15.0.0
25 |
26 | - Swift 5
27 |
28 | # v14.0.0
29 |
30 | - Minimum version of the client is now Swift 4.2.
31 | - Add exponential backoff for reconnects, with `reconnectWaitMax` and `randomizationFactor` options [#1149](https://github.com/socketio/socket.io-client-swift/pull/1149)
32 | - `statusChange` event's data format adds a second value, the raw value of the status. This is for use in Objective-C. [#1147](https://github.com/socketio/socket.io-client-swift/issues/1147)
33 |
34 | # v13.4.0
35 |
36 | - Add emits with write completion handlers. [#1096](https://github.com/socketio/socket.io-client-swift/issues/1096)
37 | - Add ability to listen for when a websocket upgrade happens
38 |
39 | # v13.3.1
40 |
41 | - 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)
42 |
43 | # v13.3.0
44 |
45 | - 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))
46 |
47 | # v13.2.1
48 |
49 | - Fix packets getting lost when WebSocket upgrade fails. [#1033](https://github.com/socketio/socket.io-client-swift/issues/1033)
50 | - Fix bad unit tests. [#794](https://github.com/socketio/socket.io-client-swift/issues/794)
51 |
52 | # v13.2.0
53 |
54 | - Add ability to bypass Data inspection in emits. [#992]((https://github.com/socketio/socket.io-client-swift/issues/992))
55 | - Allow `SocketEngine` to be subclassed
56 |
57 | # v13.1.3
58 |
59 | - Fix setting reconnectAttempts [#989]((https://github.com/socketio/socket.io-client-swift/issues/989))
60 |
61 |
62 | # v13.1.2
63 |
64 | - Fix [#950](https://github.com/socketio/socket.io-client-swift/issues/950)
65 | - Conforming to `SocketEngineWebsocket` no longer requires conforming to `WebsocketDelegate`
66 |
67 |
68 | # v13.1.1
69 |
70 | - Fix [#923](https://github.com/socketio/socket.io-client-swift/issues/923)
71 | - Fix [#894](https://github.com/socketio/socket.io-client-swift/issues/894)
72 |
73 | # v13.1.0
74 |
75 | - Allow setting `SocketEngineSpec.extraHeaders` after init.
76 | - Deprecate `SocketEngineSpec.websocket` in favor of just using the `SocketEngineSpec.polling` property.
77 | - Enable bitcode for most platforms.
78 | - Fix [#882](https://github.com/socketio/socket.io-client-swift/issues/882). This adds a new method
79 | `SocketManger.removeSocket(_:)` that should be called if when you no longer wish to use a socket again.
80 | This will cause the engine to no longer keep a strong reference to the socket and no longer track it.
81 |
82 | # v13.0.1
83 |
84 | - Fix not setting handleQueue on `SocketManager`
85 |
86 | # v13.0.0
87 |
88 | Checkout out the migration guide in Usage Docs for a more detailed guide on how to migrate to this version.
89 |
90 | What's new:
91 | ---
92 |
93 | - Adds a new `SocketManager` class that multiplexes multiple namespaces through a single engine.
94 | - Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
95 | - watchOS support.
96 |
97 | Important API changes
98 | ---
99 |
100 | - Many properties that were previously on `SocketIOClient` have been moved to the `SocketManager`.
101 | - `SocketIOClientOption.nsp` has been removed. Use `SocketManager.socket(forNamespace:)` to create/get a socket attached to a specific namespace.
102 | - Adds `.sentPing` and `.gotPong` client events for tracking ping/pongs.
103 | - Makes the framework a single target.
104 | - Updates Starscream to 3.0
105 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "daltoniam/Starscream" ~> 4.0.8
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "daltoniam/Starscream" "4.0.8"
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Erik Little
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 |
24 |
25 | This library makes use of the following third party libraries:
26 |
27 | Starscream
28 | ----------
29 |
30 | Apache License
31 | Version 2.0, January 2004
32 | http://www.apache.org/licenses/
33 |
34 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
35 |
36 | 1. Definitions.
37 |
38 | "License" shall mean the terms and conditions for use, reproduction,
39 | and distribution as defined by Sections 1 through 9 of this document.
40 |
41 | "Licensor" shall mean the copyright owner or entity authorized by
42 | the copyright owner that is granting the License.
43 |
44 | "Legal Entity" shall mean the union of the acting entity and all
45 | other entities that control, are controlled by, or are under common
46 | control with that entity. For the purposes of this definition,
47 | "control" means (i) the power, direct or indirect, to cause the
48 | direction or management of such entity, whether by contract or
49 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
50 | outstanding shares, or (iii) beneficial ownership of such entity.
51 |
52 | "You" (or "Your") shall mean an individual or Legal Entity
53 | exercising permissions granted by this License.
54 |
55 | "Source" form shall mean the preferred form for making modifications,
56 | including but not limited to software source code, documentation
57 | source, and configuration files.
58 |
59 | "Object" form shall mean any form resulting from mechanical
60 | transformation or translation of a Source form, including but
61 | not limited to compiled object code, generated documentation,
62 | and conversions to other media types.
63 |
64 | "Work" shall mean the work of authorship, whether in Source or
65 | Object form, made available under the License, as indicated by a
66 | copyright notice that is included in or attached to the work
67 | (an example is provided in the Appendix below).
68 |
69 | "Derivative Works" shall mean any work, whether in Source or Object
70 | form, that is based on (or derived from) the Work and for which the
71 | editorial revisions, annotations, elaborations, or other modifications
72 | represent, as a whole, an original work of authorship. For the purposes
73 | of this License, Derivative Works shall not include works that remain
74 | separable from, or merely link (or bind by name) to the interfaces of,
75 | the Work and Derivative Works thereof.
76 |
77 | "Contribution" shall mean any work of authorship, including
78 | the original version of the Work and any modifications or additions
79 | to that Work or Derivative Works thereof, that is intentionally
80 | submitted to Licensor for inclusion in the Work by the copyright owner
81 | or by an individual or Legal Entity authorized to submit on behalf of
82 | the copyright owner. For the purposes of this definition, "submitted"
83 | means any form of electronic, verbal, or written communication sent
84 | to the Licensor or its representatives, including but not limited to
85 | communication on electronic mailing lists, source code control systems,
86 | and issue tracking systems that are managed by, or on behalf of, the
87 | Licensor for the purpose of discussing and improving the Work, but
88 | excluding communication that is conspicuously marked or otherwise
89 | designated in writing by the copyright owner as "Not a Contribution."
90 |
91 | "Contributor" shall mean Licensor and any individual or Legal Entity
92 | on behalf of whom a Contribution has been received by Licensor and
93 | subsequently incorporated within the Work.
94 |
95 | 2. Grant of Copyright License. Subject to the terms and conditions of
96 | this License, each Contributor hereby grants to You a perpetual,
97 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
98 | copyright license to reproduce, prepare Derivative Works of,
99 | publicly display, publicly perform, sublicense, and distribute the
100 | Work and such Derivative Works in Source or Object form.
101 |
102 | 3. Grant of Patent License. Subject to the terms and conditions of
103 | this License, each Contributor hereby grants to You a perpetual,
104 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
105 | (except as stated in this section) patent license to make, have made,
106 | use, offer to sell, sell, import, and otherwise transfer the Work,
107 | where such license applies only to those patent claims licensable
108 | by such Contributor that are necessarily infringed by their
109 | Contribution(s) alone or by combination of their Contribution(s)
110 | with the Work to which such Contribution(s) was submitted. If You
111 | institute patent litigation against any entity (including a
112 | cross-claim or counterclaim in a lawsuit) alleging that the Work
113 | or a Contribution incorporated within the Work constitutes direct
114 | or contributory patent infringement, then any patent licenses
115 | granted to You under this License for that Work shall terminate
116 | as of the date such litigation is filed.
117 |
118 | 4. Redistribution. You may reproduce and distribute copies of the
119 | Work or Derivative Works thereof in any medium, with or without
120 | modifications, and in Source or Object form, provided that You
121 | meet the following conditions:
122 |
123 | (a) You must give any other recipients of the Work or
124 | Derivative Works a copy of this License; and
125 |
126 | (b) You must cause any modified files to carry prominent notices
127 | stating that You changed the files; and
128 |
129 | (c) You must retain, in the Source form of any Derivative Works
130 | that You distribute, all copyright, patent, trademark, and
131 | attribution notices from the Source form of the Work,
132 | excluding those notices that do not pertain to any part of
133 | the Derivative Works; and
134 |
135 | (d) If the Work includes a "NOTICE" text file as part of its
136 | distribution, then any Derivative Works that You distribute must
137 | include a readable copy of the attribution notices contained
138 | within such NOTICE file, excluding those notices that do not
139 | pertain to any part of the Derivative Works, in at least one
140 | of the following places: within a NOTICE text file distributed
141 | as part of the Derivative Works; within the Source form or
142 | documentation, if provided along with the Derivative Works; or,
143 | within a display generated by the Derivative Works, if and
144 | wherever such third-party notices normally appear. The contents
145 | of the NOTICE file are for informational purposes only and
146 | do not modify the License. You may add Your own attribution
147 | notices within Derivative Works that You distribute, alongside
148 | or as an addendum to the NOTICE text from the Work, provided
149 | that such additional attribution notices cannot be construed
150 | as modifying the License.
151 |
152 | You may add Your own copyright statement to Your modifications and
153 | may provide additional or different license terms and conditions
154 | for use, reproduction, or distribution of Your modifications, or
155 | for any such Derivative Works as a whole, provided Your use,
156 | reproduction, and distribution of the Work otherwise complies with
157 | the conditions stated in this License.
158 |
159 | 5. Submission of Contributions. Unless You explicitly state otherwise,
160 | any Contribution intentionally submitted for inclusion in the Work
161 | by You to the Licensor shall be under the terms and conditions of
162 | this License, without any additional terms or conditions.
163 | Notwithstanding the above, nothing herein shall supersede or modify
164 | the terms of any separate license agreement you may have executed
165 | with Licensor regarding such Contributions.
166 |
167 | 6. Trademarks. This License does not grant permission to use the trade
168 | names, trademarks, service marks, or product names of the Licensor,
169 | except as required for reasonable and customary use in describing the
170 | origin of the Work and reproducing the content of the NOTICE file.
171 |
172 | 7. Disclaimer of Warranty. Unless required by applicable law or
173 | agreed to in writing, Licensor provides the Work (and each
174 | Contributor provides its Contributions) on an "AS IS" BASIS,
175 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
176 | implied, including, without limitation, any warranties or conditions
177 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
178 | PARTICULAR PURPOSE. You are solely responsible for determining the
179 | appropriateness of using or redistributing the Work and assume any
180 | risks associated with Your exercise of permissions under this License.
181 |
182 | 8. Limitation of Liability. In no event and under no legal theory,
183 | whether in tort (including negligence), contract, or otherwise,
184 | unless required by applicable law (such as deliberate and grossly
185 | negligent acts) or agreed to in writing, shall any Contributor be
186 | liable to You for damages, including any direct, indirect, special,
187 | incidental, or consequential damages of any character arising as a
188 | result of this License or out of the use or inability to use the
189 | Work (including but not limited to damages for loss of goodwill,
190 | work stoppage, computer failure or malfunction, or any and all
191 | other commercial damages or losses), even if such Contributor
192 | has been advised of the possibility of such damages.
193 |
194 | 9. Accepting Warranty or Additional Liability. While redistributing
195 | the Work or Derivative Works thereof, You may choose to offer,
196 | and charge a fee for, acceptance of support, warranty, indemnity,
197 | or other liability obligations and/or rights consistent with this
198 | License. However, in accepting such obligations, You may act only
199 | on Your own behalf and on Your sole responsibility, not on behalf
200 | of any other Contributor, and only if You agree to indemnify,
201 | defend, and hold each Contributor harmless for any liability
202 | incurred by, or claims asserted against, such Contributor by reason
203 | of your accepting any such warranty or additional liability.
204 |
205 | END OF TERMS AND CONDITIONS
206 |
207 | APPENDIX: How to apply the Apache License to your work.
208 |
209 | To apply the Apache License to your work, attach the following
210 | boilerplate notice, with the fields enclosed by brackets "{}"
211 | replaced with your own identifying information. (Don't include
212 | the brackets!) The text should be enclosed in the appropriate
213 | comment syntax for the file format. We also recommend that a
214 | file or class name and description of purpose be included on the
215 | same "printed page" as the copyright notice for easier
216 | identification within third-party archives.
217 |
218 | Copyright {yyyy} {name of copyright owner}
219 |
220 | Licensed under the Apache License, Version 2.0 (the "License");
221 | you may not use this file except in compliance with the License.
222 | You may obtain a copy of the License at
223 |
224 | http://www.apache.org/licenses/LICENSE-2.0
225 |
226 | Unless required by applicable law or agreed to in writing, software
227 | distributed under the License is distributed on an "AS IS" BASIS,
228 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
229 | See the License for the specific language governing permissions and
230 | limitations under the License.
231 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Starscream",
6 | "repositoryURL": "https://github.com/daltoniam/Starscream",
7 | "state": {
8 | "branch": null,
9 | "revision": "c6bfd1af48efcc9a9ad203665db12375ba6b145a",
10 | "version": "4.0.8"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.4
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", .upToNextMajor(from: "4.0.8")),
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 | if data.first as? String ?? "passed" == SocketAckStatus.noAck {
22 | // Handle ack timeout
23 | }
24 |
25 | socket.emit("update", ["amount": cur + 2.50])
26 | }
27 |
28 | ack.with("Got your currentAmount", "dude")
29 | }
30 |
31 | socket.connect()
32 | ```
33 |
34 | ## Features
35 | - Supports Socket.IO server 2.0+/3.0+/4.0+ (see the [compatibility table](https://nuclearace.github.io/Socket.IO-Client-Swift/Compatibility.html))
36 | - Supports Binary
37 | - Supports Polling and WebSockets
38 | - Supports TLS/SSL
39 |
40 | ## FAQS
41 | Checkout the [FAQs](https://nuclearace.github.io/Socket.IO-Client-Swift/faq.html) for commonly asked questions.
42 |
43 |
44 | Checkout the [12to13](https://nuclearace.github.io/Socket.IO-Client-Swift/12to13.html) guide for migrating to v13+ from v12 below.
45 |
46 | Checkout the [15to16](https://nuclearace.github.io/Socket.IO-Client-Swift/15to16.html) guide for migrating to v16+ from v15.
47 |
48 | ## Installation
49 | Requires Swift 4/5 and Xcode 10.x
50 |
51 | ### Swift Package Manager
52 | Add the project as a dependency to your Package.swift:
53 | ```swift
54 | // swift-tools-version:4.2
55 |
56 | import PackageDescription
57 |
58 | let package = Package(
59 | name: "socket.io-test",
60 | products: [
61 | .executable(name: "socket.io-test", targets: ["YourTargetName"])
62 | ],
63 | dependencies: [
64 | .package(url: "https://github.com/socketio/socket.io-client-swift", .upToNextMinor(from: "16.1.1"))
65 | ],
66 | targets: [
67 | .target(name: "YourTargetName", dependencies: ["SocketIO"], path: "./Path/To/Your/Sources")
68 | ]
69 | )
70 | ```
71 |
72 | Then import `import SocketIO`.
73 |
74 | ### Carthage
75 | Add this line to your `Cartfile`:
76 | ```
77 | github "socketio/socket.io-client-swift" ~> 16.1.1
78 | ```
79 |
80 | Run `carthage update --platform ios,macosx`.
81 |
82 | Add the `Starscream` and `SocketIO` frameworks to your projects and follow the usual Carthage process.
83 |
84 | ### CocoaPods 1.0.0 or later
85 | Create `Podfile` and add `pod 'Socket.IO-Client-Swift'`:
86 |
87 | ```ruby
88 | use_frameworks!
89 |
90 | target 'YourApp' do
91 | pod 'Socket.IO-Client-Swift', '~> 16.1.1'
92 | end
93 | ```
94 |
95 | Install pods:
96 |
97 | ```
98 | $ pod install
99 | ```
100 |
101 | Import the module:
102 |
103 | Swift:
104 | ```swift
105 | import SocketIO
106 | ```
107 |
108 | Objective-C:
109 |
110 | ```Objective-C
111 | @import SocketIO;
112 | ```
113 |
114 |
115 | # [Docs](https://nuclearace.github.io/Socket.IO-Client-Swift/index.html)
116 |
117 | - [Client](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketIOClient.html)
118 | - [Manager](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketManager.html)
119 | - [Engine](https://nuclearace.github.io/Socket.IO-Client-Swift/Classes/SocketEngine.html)
120 | - [Options](https://nuclearace.github.io/Socket.IO-Client-Swift/Enums/SocketIOClientOption.html)
121 |
122 | ## Detailed Example
123 | A more detailed example can be found [here](https://github.com/nuclearace/socket.io-client-swift-example)
124 |
125 | An example using the Swift Package Manager can be found [here](https://github.com/nuclearace/socket.io-client-swift-spm-example)
126 |
127 | ## License
128 | MIT
129 |
--------------------------------------------------------------------------------
/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 = "16.1.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 3.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 = '12.0'
15 | s.osx.deployment_target = '10.13'
16 | s.tvos.deployment_target = '12.0'
17 | s.watchos.deployment_target = '5.0'
18 | s.requires_arc = true
19 | s.source = {
20 | :git => "https://github.com/socketio/socket.io-client-swift.git",
21 | :tag => 'v16.1.1',
22 | :submodules => true
23 | }
24 |
25 | s.swift_version = "5"
26 | s.pod_target_xcconfig = {
27 | 'SWIFT_VERSION' => '5.4'
28 | }
29 | s.source_files = "Source/SocketIO/**/*.swift", "Source/SocketIO/*.swift"
30 | s.dependency "Starscream", "~> 4.0.8"
31 | end
32 |
--------------------------------------------------------------------------------
/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 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/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 | /// :nodoc:
119 | deinit {
120 | DefaultSocketLogger.Logger.log("OnAckCallback for \(ackNumber) being released", type: "OnAckCallback")
121 | }
122 |
123 | // MARK: Methods
124 |
125 | /// Completes an emitWithAck. If this isn't called, the emit never happens.
126 | ///
127 | /// - parameter seconds: The number of seconds before this emit times out if an ack hasn't been received.
128 | /// - parameter callback: The callback called when an ack is received, or when a timeout happens.
129 | /// To check for timeout, use `SocketAckStatus`'s `noAck` case.
130 | @objc
131 | public func timingOut(after seconds: Double, callback: @escaping AckCallback) {
132 | guard let socket = self.socket, ackNumber != -1 else { return }
133 |
134 | socket.ackHandlers.addAck(ackNumber, callback: callback)
135 | socket.emit(items, ack: ackNumber, binary: binary)
136 |
137 | guard seconds != 0 else { return }
138 |
139 | socket.manager?.handleQueue.asyncAfter(deadline: DispatchTime.now() + seconds) {[weak socket] in
140 | guard let socket = socket else { return }
141 |
142 | socket.ackHandlers.timeoutAck(self.ackNumber)
143 | }
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/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 | /// Tests whether a string is equal to a given SocketAckStatus
36 | public static func == (lhs: String, rhs: SocketAckStatus) -> Bool {
37 | return lhs == rhs.rawValue
38 | }
39 |
40 | /// Tests whether a string is equal to a given SocketAckStatus
41 | public static func == (lhs: SocketAckStatus, rhs: String) -> Bool {
42 | return rhs == lhs
43 | }
44 | }
45 |
46 | private struct SocketAck : Hashable {
47 | let ack: Int
48 | var callback: AckCallback!
49 |
50 | init(ack: Int) {
51 | self.ack = ack
52 | }
53 |
54 | init(ack: Int, callback: @escaping AckCallback) {
55 | self.ack = ack
56 | self.callback = callback
57 | }
58 |
59 | func hash(into hasher: inout Hasher) {
60 | ack.hash(into: &hasher)
61 | }
62 |
63 | fileprivate static func <(lhs: SocketAck, rhs: SocketAck) -> Bool {
64 | return lhs.ack < rhs.ack
65 | }
66 |
67 | fileprivate static func ==(lhs: SocketAck, rhs: SocketAck) -> Bool {
68 | return lhs.ack == rhs.ack
69 | }
70 | }
71 |
72 | class SocketAckManager {
73 | private var acks = Set(minimumCapacity: 1)
74 |
75 | func addAck(_ ack: Int, callback: @escaping AckCallback) {
76 | acks.insert(SocketAck(ack: ack, callback: callback))
77 | }
78 |
79 | /// Should be called on handle queue
80 | func executeAck(_ ack: Int, with items: [Any]) {
81 | acks.remove(SocketAck(ack: ack))?.callback(items)
82 | }
83 |
84 | /// Should be called on handle queue
85 | func timeoutAck(_ ack: Int) {
86 | acks.remove(SocketAck(ack: ack))?.callback?([SocketAckStatus.noAck.rawValue])
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/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
39 | }
40 |
41 | /// The options for a client.
42 | public enum SocketIOClientOption : ClientOption {
43 | /// If given, the WebSocket transport will attempt to use compression.
44 | case compress
45 |
46 | /// A dictionary of GET parameters that will be included in the connect url.
47 | case connectParams([String: Any])
48 |
49 | /// An array of cookies that will be sent during the initial connection.
50 | case cookies([HTTPCookie])
51 |
52 | /// Any extra HTTP headers that should be sent during the initial connection.
53 | case extraHeaders([String: String])
54 |
55 | /// If passed `true`, will cause the client to always create a new engine. Useful for debugging,
56 | /// or when you want to be sure no state from previous engines is being carried over.
57 | case forceNew(Bool)
58 |
59 | /// If passed `true`, the only transport that will be used will be HTTP long-polling.
60 | case forcePolling(Bool)
61 |
62 | /// If passed `true`, the only transport that will be used will be WebSockets.
63 | case forceWebsockets(Bool)
64 |
65 | /// If passed `true`, the WebSocket stream will be configured with the enableSOCKSProxy `true`.
66 | case enableSOCKSProxy(Bool)
67 |
68 | /// The queue that all interaction with the client should occur on. This is the queue that event handlers are
69 | /// called on.
70 | ///
71 | /// **This should be a serial queue! Concurrent queues are not supported and might cause crashes and races**.
72 | case handleQueue(DispatchQueue)
73 |
74 | /// If passed `true`, the client will log debug information. This should be turned off in production code.
75 | case log(Bool)
76 |
77 | /// Used to pass in a custom logger.
78 | case logger(SocketLogger)
79 |
80 | /// A custom path to socket.io. Only use this if the socket.io server is configured to look for this path.
81 | case path(String)
82 |
83 | /// If passed `false`, the client will not reconnect when it loses connection. Useful if you want full control
84 | /// over when reconnects happen.
85 | case reconnects(Bool)
86 |
87 | /// The number of times to try and reconnect before giving up. Pass `-1` to [never give up](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
88 | case reconnectAttempts(Int)
89 |
90 | /// The minimum number of seconds to wait before reconnect attempts.
91 | case reconnectWait(Int)
92 |
93 | /// The maximum number of seconds to wait before reconnect attempts.
94 | case reconnectWaitMax(Int)
95 |
96 | /// The randomization factor for calculating reconnect jitter.
97 | case randomizationFactor(Double)
98 |
99 | /// Set `true` if your server is using secure transports.
100 | case secure(Bool)
101 |
102 | /// Allows you to set which certs are valid. Useful for SSL pinning.
103 | case security(CertificatePinning)
104 |
105 | /// If you're using a self-signed set. Only use for development.
106 | case selfSigned(Bool)
107 |
108 | /// Sets an NSURLSessionDelegate for the underlying engine. Useful if you need to handle self-signed certs.
109 | case sessionDelegate(URLSessionDelegate)
110 |
111 | /// If passed `false`, the WebSocket stream will be configured with the useCustomEngine `false`.
112 | case useCustomEngine(Bool)
113 |
114 | /// The version of socket.io being used. This should match the server version. Default is 3.
115 | case version(SocketIOVersion)
116 |
117 | // MARK: Properties
118 |
119 | /// The description of this option.
120 | public var description: String {
121 | let description: String
122 |
123 | switch self {
124 | case .compress:
125 | description = "compress"
126 | case .connectParams:
127 | description = "connectParams"
128 | case .cookies:
129 | description = "cookies"
130 | case .extraHeaders:
131 | description = "extraHeaders"
132 | case .forceNew:
133 | description = "forceNew"
134 | case .forcePolling:
135 | description = "forcePolling"
136 | case .forceWebsockets:
137 | description = "forceWebsockets"
138 | case .handleQueue:
139 | description = "handleQueue"
140 | case .log:
141 | description = "log"
142 | case .logger:
143 | description = "logger"
144 | case .path:
145 | description = "path"
146 | case .reconnects:
147 | description = "reconnects"
148 | case .reconnectAttempts:
149 | description = "reconnectAttempts"
150 | case .reconnectWait:
151 | description = "reconnectWait"
152 | case .reconnectWaitMax:
153 | description = "reconnectWaitMax"
154 | case .randomizationFactor:
155 | description = "randomizationFactor"
156 | case .secure:
157 | description = "secure"
158 | case .selfSigned:
159 | description = "selfSigned"
160 | case .security:
161 | description = "security"
162 | case .sessionDelegate:
163 | description = "sessionDelegate"
164 | case .enableSOCKSProxy:
165 | description = "enableSOCKSProxy"
166 | case .useCustomEngine:
167 | description = "customEngine"
168 | case .version:
169 | description = "version"
170 | }
171 |
172 | return description
173 | }
174 |
175 | func getSocketIOOptionValue() -> Any {
176 | let value: Any
177 |
178 | switch self {
179 | case .compress:
180 | value = true
181 | case let .connectParams(params):
182 | value = params
183 | case let .cookies(cookies):
184 | value = cookies
185 | case let .extraHeaders(headers):
186 | value = headers
187 | case let .forceNew(force):
188 | value = force
189 | case let .forcePolling(force):
190 | value = force
191 | case let .forceWebsockets(force):
192 | value = force
193 | case let .handleQueue(queue):
194 | value = queue
195 | case let .log(log):
196 | value = log
197 | case let .logger(logger):
198 | value = logger
199 | case let .path(path):
200 | value = path
201 | case let .reconnects(reconnects):
202 | value = reconnects
203 | case let .reconnectAttempts(attempts):
204 | value = attempts
205 | case let .reconnectWait(wait):
206 | value = wait
207 | case let .reconnectWaitMax(wait):
208 | value = wait
209 | case let .randomizationFactor(factor):
210 | value = factor
211 | case let .secure(secure):
212 | value = secure
213 | case let .security(security):
214 | value = security
215 | case let .selfSigned(signed):
216 | value = signed
217 | case let .sessionDelegate(delegate):
218 | value = delegate
219 | case let .enableSOCKSProxy(enable):
220 | value = enable
221 | case let .useCustomEngine(enable):
222 | value = enable
223 | case let.version(versionNum):
224 | value = versionNum
225 | }
226 |
227 | return value
228 | }
229 |
230 | // MARK: Operators
231 |
232 | /// Compares whether two options are the same.
233 | ///
234 | /// - parameter lhs: Left operand to compare.
235 | /// - parameter rhs: Right operand to compare.
236 | /// - returns: `true` if the two are the same option.
237 | public static func ==(lhs: SocketIOClientOption, rhs: SocketIOClientOption) -> Bool {
238 | return lhs.description == rhs.description
239 | }
240 |
241 | }
242 |
--------------------------------------------------------------------------------
/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 ping message. Only called in socket.io >3.
48 | func engineDidReceivePing()
49 |
50 | /// Called when the engine receives a pong message. Only called in socket.io 2.
51 | func engineDidReceivePong()
52 |
53 | /// Called when the engine sends a ping to the server. Only called in socket.io 2.
54 | func engineDidSendPing()
55 |
56 | /// Called when the engine sends a pong to the server. Only called in socket.io >3.
57 | func engineDidSendPong()
58 |
59 | /// Called when the engine has a message that must be parsed.
60 | ///
61 | /// - parameter msg: The message that needs parsing.
62 | func parseEngineMessage(_ msg: String)
63 |
64 | /// Called when the engine receives binary data.
65 | ///
66 | /// - parameter data: The data the engine received.
67 | func parseEngineBinaryData(_ data: Data)
68 |
69 | /// Called when when upgrading the http connection to a websocket connection.
70 | ///
71 | /// - parameter headers: The http headers.
72 | func engineDidWebsocketUpgrade(headers: [String: String])
73 | }
74 |
--------------------------------------------------------------------------------
/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: [Post] { 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], completion: (() -> ())?)
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 {
78 | for packet in postWait { packet.completion?() }
79 | postWait.removeAll(keepingCapacity: true)
80 | }
81 |
82 | var postStr = ""
83 |
84 | if version.rawValue >= 3 {
85 | postStr = postWait.lazy.map({ $0.msg }).joined(separator: "\u{1e}")
86 | } else {
87 | for packet in postWait {
88 | postStr += "\(packet.msg.utf16.count):\(packet.msg)"
89 | }
90 | }
91 |
92 | DefaultSocketLogger.Logger.log("Created POST string: \(postStr)", type: "SocketEnginePolling")
93 |
94 | var req = URLRequest(url: urlPollingWithSid)
95 | let postData = postStr.data(using: .utf8, allowLossyConversion: false)!
96 |
97 | addHeaders(to: &req)
98 |
99 | req.httpMethod = "POST"
100 | req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type")
101 | req.httpBody = postData
102 | req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length")
103 |
104 | return req
105 | }
106 |
107 | /// Call to send a long-polling request.
108 | ///
109 | /// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request.
110 | public func doPoll() {
111 | guard polling && !waitingForPoll && connected && !closed else { return }
112 |
113 | var req = URLRequest(url: urlPollingWithSid)
114 | addHeaders(to: &req)
115 |
116 | doLongPoll(for: req)
117 | }
118 |
119 | func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> ()) {
120 | guard polling && !closed && !invalidated && !fastUpgrade else { return }
121 |
122 | DefaultSocketLogger.Logger.log("Doing polling \(req.httpMethod ?? "") \(req)", type: "SocketEnginePolling")
123 |
124 | session?.dataTask(with: req, completionHandler: callback).resume()
125 | }
126 |
127 | func doLongPoll(for req: URLRequest) {
128 | waitingForPoll = true
129 |
130 | doRequest(for: req) {[weak self] data, res, err in
131 | guard let this = self, this.polling else { return }
132 | guard let data = data, let res = res as? HTTPURLResponse, res.statusCode == 200 else {
133 | if let err = err {
134 | DefaultSocketLogger.Logger.error(err.localizedDescription, type: "SocketEnginePolling")
135 | } else {
136 | DefaultSocketLogger.Logger.error("Error during long poll request", type: "SocketEnginePolling")
137 | }
138 |
139 | if this.polling {
140 | this.didError(reason: err?.localizedDescription ?? "Error")
141 | }
142 |
143 | return
144 | }
145 |
146 | DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling")
147 |
148 | if let str = String(data: data, encoding: .utf8) {
149 | this.parsePollingMessage(str)
150 | }
151 |
152 | this.waitingForPoll = false
153 |
154 | if this.fastUpgrade {
155 | this.doFastUpgrade()
156 | } else if !this.closed && this.polling {
157 | this.doPoll()
158 | }
159 | }
160 | }
161 |
162 | private func flushWaitingForPost() {
163 | guard postWait.count != 0 && connected else { return }
164 | guard polling else {
165 | flushWaitingForPostToWebSocket()
166 |
167 | return
168 | }
169 |
170 | let req = createRequestForPostWithPostWait()
171 |
172 | waitingForPost = true
173 |
174 | DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling")
175 |
176 | doRequest(for: req) {[weak self] _, res, err in
177 | guard let this = self else { return }
178 | guard let res = res as? HTTPURLResponse, res.statusCode == 200 else {
179 | if let err = err {
180 | DefaultSocketLogger.Logger.error(err.localizedDescription, type: "SocketEnginePolling")
181 | } else {
182 | DefaultSocketLogger.Logger.error("Error flushing waiting posts", type: "SocketEnginePolling")
183 | }
184 |
185 | if this.polling {
186 | this.didError(reason: err?.localizedDescription ?? "Error")
187 | }
188 |
189 | return
190 | }
191 |
192 | this.waitingForPost = false
193 |
194 | if !this.fastUpgrade {
195 | this.flushWaitingForPost()
196 | this.doPoll()
197 | }
198 | }
199 | }
200 |
201 | func parsePollingMessage(_ str: String) {
202 | guard !str.isEmpty else { return }
203 |
204 | DefaultSocketLogger.Logger.log("Got poll message: \(str)", type: "SocketEnginePolling")
205 |
206 | if version.rawValue >= 3 {
207 | let records = str.components(separatedBy: "\u{1e}")
208 |
209 | for record in records {
210 | parseEngineMessage(record)
211 | }
212 | } else {
213 | guard str.count != 1 else {
214 | parseEngineMessage(str)
215 |
216 | return
217 | }
218 |
219 | var reader = SocketStringReader(message: str)
220 |
221 | while reader.hasNext {
222 | if let n = Int(reader.readUntilOccurence(of: ":")) {
223 | parseEngineMessage(reader.read(count: n))
224 | } else {
225 | parseEngineMessage(str)
226 | break
227 | }
228 | }
229 | }
230 | }
231 |
232 | /// Sends an engine.io message through the polling transport.
233 | ///
234 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
235 | ///
236 | /// - parameter message: The message to send.
237 | /// - parameter withType: The type of message to send.
238 | /// - parameter withData: The data associated with this message.
239 | /// - parameter completion: Callback called on transport write completion.
240 | public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data], completion: (() -> ())? = nil) {
241 | DefaultSocketLogger.Logger.log("Sending poll: \(message) as type: \(type.rawValue)", type: "SocketEnginePolling")
242 |
243 | postWait.append((String(type.rawValue) + message, completion))
244 |
245 | for data in datas {
246 | if case let .right(bin) = createBinaryDataForSend(using: data) {
247 | postWait.append((bin, {}))
248 | }
249 | }
250 |
251 | if !waitingForPost {
252 | flushWaitingForPost()
253 | }
254 | }
255 |
256 | /// Call to stop polling and invalidate the URLSession.
257 | public func stopPolling() {
258 | waitingForPoll = false
259 | waitingForPost = false
260 | session?.finishTasksAndInvalidate()
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/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 | public protocol SocketEngineSpec: AnyObject {
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 | /// The version of engine.io being used. Default is three.
85 | var version: SocketIOVersion { get }
86 |
87 | /// If `true`, then the engine is currently in WebSockets mode.
88 | @available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets")
89 | var websocket: Bool { get }
90 |
91 | /// The WebSocket for this engine.
92 | var ws: WebSocket? { get }
93 |
94 | // MARK: Initializers
95 |
96 | /// Creates a new engine.
97 | ///
98 | /// - parameter client: The client for this engine.
99 | /// - parameter url: The url for this engine.
100 | /// - parameter options: The options for this engine.
101 | init(client: SocketEngineClient, url: URL, options: [String: Any]?)
102 |
103 | // MARK: Methods
104 |
105 | /// Starts the connection to the server.
106 | func connect()
107 |
108 | /// Called when an error happens during execution. Causes a disconnection.
109 | func didError(reason: String)
110 |
111 | /// Disconnects from the server.
112 | ///
113 | /// - parameter reason: The reason for the disconnection. This is communicated up to the client.
114 | func disconnect(reason: String)
115 |
116 | /// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in
117 | /// WebSocket mode.
118 | ///
119 | /// **You shouldn't call this directly**
120 | func doFastUpgrade()
121 |
122 | /// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when
123 | /// the engine is attempting to upgrade to WebSocket it does not do any POSTing.
124 | ///
125 | /// **You shouldn't call this directly**
126 | func flushWaitingForPostToWebSocket()
127 |
128 | /// Parses raw binary received from engine.io.
129 | ///
130 | /// - parameter data: The data to parse.
131 | func parseEngineData(_ data: Data)
132 |
133 | /// Parses a raw engine.io packet.
134 | ///
135 | /// - parameter message: The message to parse.
136 | func parseEngineMessage(_ message: String)
137 |
138 | /// Writes a message to engine.io, independent of transport.
139 | ///
140 | /// - parameter msg: The message to send.
141 | /// - parameter type: The type of this message.
142 | /// - parameter data: Any data that this message has.
143 | /// - parameter completion: Callback called on transport write completion.
144 | func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: (() -> ())?)
145 | }
146 |
147 | extension SocketEngineSpec {
148 | var engineIOParam: String {
149 | switch version {
150 | case .two:
151 | return "&EIO=3"
152 | case .three:
153 | return "&EIO=4"
154 | }
155 | }
156 |
157 | var urlPollingWithSid: URL {
158 | var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)!
159 | com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)"
160 |
161 | if !com.percentEncodedQuery!.contains("EIO") {
162 | com.percentEncodedQuery = com.percentEncodedQuery! + engineIOParam
163 | }
164 |
165 | return com.url!
166 | }
167 |
168 | var urlWebSocketWithSid: URL {
169 | var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)!
170 | com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)")
171 |
172 | if !com.percentEncodedQuery!.contains("EIO") {
173 | com.percentEncodedQuery = com.percentEncodedQuery! + engineIOParam
174 | }
175 |
176 |
177 | return com.url!
178 | }
179 |
180 | func addHeaders(to req: inout URLRequest, includingCookies additionalCookies: [HTTPCookie]? = nil) {
181 | var cookiesToAdd: [HTTPCookie] = cookies ?? []
182 | cookiesToAdd += additionalCookies ?? []
183 |
184 | if !cookiesToAdd.isEmpty {
185 | req.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookiesToAdd)
186 | }
187 |
188 | if let extraHeaders = extraHeaders {
189 | for (headerName, value) in extraHeaders {
190 | req.setValue(value, forHTTPHeaderField: headerName)
191 | }
192 | }
193 | }
194 |
195 | func createBinaryDataForSend(using data: Data) -> Either {
196 | let prefixB64 = version.rawValue >= 3 ? "b" : "b4"
197 |
198 | if polling {
199 | return .right(prefixB64 + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)))
200 | } else {
201 | return .left(version.rawValue >= 3 ? data : Data([0x4]) + data)
202 | }
203 | }
204 |
205 | /// Send an engine message (4)
206 | func send(_ msg: String, withData datas: [Data], completion: (() -> ())? = nil) {
207 | write(msg, withType: .message, withData: datas, completion: completion)
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/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: Properties
32 |
33 | /// Whether or not the ws is connected
34 | var wsConnected: Bool { get }
35 |
36 | // MARK: Methods
37 |
38 | /// Sends an engine.io message through the WebSocket transport.
39 | ///
40 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
41 | ///
42 | /// - parameter message: The message to send.
43 | /// - parameter withType: The type of message to send.
44 | /// - parameter withData: The data associated with this message.
45 | /// - parameter completion: Callback called on transport write completion.
46 | func sendWebSocketMessage(_ str: String,
47 | withType type: SocketEnginePacketType,
48 | withData datas: [Data],
49 | completion: (() -> ())?)
50 | }
51 |
52 | // WebSocket methods
53 | extension SocketEngineWebsocket {
54 | func probeWebSocket() {
55 | if wsConnected {
56 | sendWebSocketMessage("probe", withType: .ping, withData: [], completion: nil)
57 | }
58 | }
59 |
60 | /// Sends an engine.io message through the WebSocket transport.
61 | ///
62 | /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
63 | ///
64 | /// - parameter message: The message to send.
65 | /// - parameter withType: The type of message to send.
66 | /// - parameter withData: The data associated with this message.
67 | /// - parameter completion: Callback called on transport write completion.
68 | public func sendWebSocketMessage(_ str: String,
69 | withType type: SocketEnginePacketType,
70 | withData data: [Data],
71 | completion: (() -> ())?
72 | ) {
73 | DefaultSocketLogger.Logger.log("Sending ws: \(str) as type: \(type.rawValue)", type: "SocketEngineWebSocket")
74 |
75 | ws?.write(string: "\(type.rawValue)\(str)")
76 |
77 | for item in data {
78 | if case let .left(bin) = createBinaryDataForSend(using: item) {
79 | ws?.write(data: bin, completion: completion)
80 | }
81 | }
82 |
83 | if data.count == 0 {
84 | completion?()
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/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 | public protocol SocketManagerSpec : SocketEngineClient {
49 | // MARK: Properties
50 |
51 | /// Returns the socket associated with the default namespace ("/").
52 | var defaultSocket: SocketIOClient { get }
53 |
54 | /// The engine for this manager.
55 | var engine: SocketEngineSpec? { get set }
56 |
57 | /// If `true` then every time `connect` is called, a new engine will be created.
58 | var forceNew: Bool { get set }
59 |
60 | // TODO Per socket queues?
61 | /// The queue that all interaction with the client should occur on. This is the queue that event handlers are
62 | /// called on.
63 | var handleQueue: DispatchQueue { get set }
64 |
65 | /// The sockets in this manager indexed by namespace.
66 | var nsps: [String: SocketIOClient] { get set }
67 |
68 | /// If `true`, this manager will try and reconnect on any disconnects.
69 | var reconnects: Bool { get set }
70 |
71 | /// The minimum number of seconds to wait before attempting to reconnect.
72 | var reconnectWait: Int { get set }
73 |
74 | /// The maximum number of seconds to wait before attempting to reconnect.
75 | var reconnectWaitMax: Int { get set }
76 |
77 | /// The randomization factor for calculating reconnect jitter.
78 | var randomizationFactor: Double { get set }
79 |
80 | /// The URL of the socket.io server.
81 | var socketURL: URL { get }
82 |
83 | /// The status of this manager.
84 | var status: SocketIOStatus { get }
85 |
86 | /// The version of socket.io in use.
87 | var version: SocketIOVersion { get }
88 |
89 | // MARK: Methods
90 |
91 | /// Connects the underlying transport.
92 | func connect()
93 |
94 | /// Connects a socket through this manager's engine.
95 | ///
96 | /// - parameter socket: The socket who we should connect through this manager.
97 | /// - parameter withPayload: Optional payload to send on connect
98 | func connectSocket(_ socket: SocketIOClient, withPayload: [String: Any]?)
99 |
100 | /// Called when the manager has disconnected from socket.io.
101 | ///
102 | /// - parameter reason: The reason for the disconnection.
103 | func didDisconnect(reason: String)
104 |
105 | /// Disconnects the manager and all associated sockets.
106 | func disconnect()
107 |
108 | /// Disconnects the given socket.
109 | ///
110 | /// - parameter socket: The socket to disconnect.
111 | func disconnectSocket(_ socket: SocketIOClient)
112 |
113 | /// Disconnects the socket associated with `forNamespace`.
114 | ///
115 | /// - parameter nsp: The namespace to disconnect from.
116 | func disconnectSocket(forNamespace nsp: String)
117 |
118 | /// Sends an event to the server on all namespaces in this manager.
119 | ///
120 | /// - parameter event: The event to send.
121 | /// - parameter items: The data to send with this event.
122 | func emitAll(_ event: String, _ items: SocketData...)
123 |
124 | /// Tries to reconnect to the server.
125 | ///
126 | /// This will cause a `disconnect` event to be emitted, as well as an `reconnectAttempt` event.
127 | func reconnect()
128 |
129 | /// Removes the socket from the manager's control.
130 | /// After calling this method the socket should no longer be considered usable.
131 | ///
132 | /// - parameter socket: The socket to remove.
133 | /// - returns: The socket removed, if it was owned by the manager.
134 | @discardableResult
135 | func removeSocket(_ socket: SocketIOClient) -> SocketIOClient?
136 |
137 | /// Returns a `SocketIOClient` for the given namespace. This socket shares a transport with the manager.
138 | ///
139 | /// Calling multiple times returns the same socket.
140 | ///
141 | /// Sockets created from this method are retained by the manager.
142 | /// Call one of the `disconnectSocket` methods on the implementing class to remove the socket from manager control.
143 | /// Or call `SocketIOClient.disconnect()` on the client.
144 | ///
145 | /// - parameter nsp: The namespace for the socket.
146 | /// - returns: A `SocketIOClient` for the given namespace.
147 | func socket(forNamespace nsp: String) -> SocketIOClient
148 | }
149 |
--------------------------------------------------------------------------------
/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 | 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 || type == .connect) && !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 | 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 | 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/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 ("reconnectWaitMax", wait as Int):
75 | return .reconnectWaitMax(wait)
76 | case let ("randomizationFactor", factor as Double):
77 | return .randomizationFactor(factor)
78 | case let ("secure", secure as Bool):
79 | return .secure(secure)
80 | case let ("security", security as CertificatePinning):
81 | return .security(security)
82 | case let ("selfSigned", selfSigned as Bool):
83 | return .selfSigned(selfSigned)
84 | case let ("sessionDelegate", delegate as URLSessionDelegate):
85 | return .sessionDelegate(delegate)
86 | case let ("compress", compress as Bool):
87 | return compress ? .compress : nil
88 | case let ("enableSOCKSProxy", enable as Bool):
89 | return .enableSOCKSProxy(enable)
90 | case let ("version", version as Int):
91 | return .version(SocketIOVersion(rawValue: version) ?? .three)
92 | case _:
93 | return nil
94 | }
95 | }
96 |
97 | func toSocketConfiguration() -> SocketIOClientConfiguration {
98 | var options = [] as SocketIOClientConfiguration
99 |
100 | for (rawKey, value) in self {
101 | if let opt = Dictionary.keyValueToSocketIOClientOption(key: rawKey, value: value) {
102 | options.insert(opt)
103 | }
104 | }
105 |
106 | return options
107 | }
108 | }
109 |
110 | extension String {
111 | func toArray() throws -> [Any] {
112 | guard let stringData = data(using: .utf16, allowLossyConversion: false) else { return [] }
113 | guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else {
114 | throw JSONError.notArray
115 | }
116 |
117 | return array
118 | }
119 |
120 | func toDictionary() throws -> [String: Any] {
121 | guard let binData = data(using: .utf16, allowLossyConversion: false) else { return [:] }
122 | guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? [String: Any] else {
123 | throw JSONError.notNSDictionary
124 | }
125 |
126 | return json
127 | }
128 |
129 | func urlEncode() -> String? {
130 | return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet)
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/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 | guard log else { return }
55 |
56 | abstractLog("LOG", message: message(), type: type)
57 | }
58 |
59 | /// Default implementation.
60 | func error(_ message: @autoclosure () -> String, type: String) {
61 | guard log else { return }
62 |
63 | abstractLog("ERROR", message: message(), type: type)
64 | }
65 |
66 | private func abstractLog(_ logType: String, message: @autoclosure () -> String, type: String) {
67 | NSLog("\(logType) \(type): %@", message())
68 | }
69 | }
70 |
71 | class DefaultSocketLogger : SocketLogger {
72 | static var Logger: SocketLogger = DefaultSocketLogger()
73 |
74 | var log = false
75 | }
76 |
--------------------------------------------------------------------------------
/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.firstIndex(where: { $0 == 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 | /// A typealias for a queued POST
77 | public typealias Post = (msg: String, completion: (() -> ())?)
78 |
79 | typealias JSON = [String: Any]
80 | typealias Probe = (msg: String, type: SocketEnginePacketType, data: [Data], completion: (() -> ())?)
81 | typealias ProbeWaitQueue = [Probe]
82 |
83 | enum Either {
84 | case left(E)
85 | case right(V)
86 | }
87 |
--------------------------------------------------------------------------------
/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 |
32 | func callback(_ items: [Any]) {
33 | XCTAssertEqual(items.count, 1, "Timed out ack should have one value")
34 | guard let timeoutReason = items[0] as? String else {
35 | XCTFail("Timeout reason should be a string")
36 |
37 | return
38 | }
39 |
40 | XCTAssert(timeoutReason == SocketAckStatus.noAck)
41 |
42 | callbackExpection.fulfill()
43 | }
44 |
45 | ackManager.addAck(1, callback: callback)
46 | ackManager.timeoutAck(1)
47 |
48 | waitForExpectations(timeout: 0.2, handler: nil)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/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 testBasicPollingMessageV3() {
14 | let expect = expectation(description: "Basic polling test v3")
15 |
16 | socket.on("blankTest") {data, ack in
17 | expect.fulfill()
18 | }
19 |
20 | engine.setConfigs([.version(.two)])
21 | engine.parsePollingMessage("15:42[\"blankTest\"]")
22 |
23 | waitForExpectations(timeout: 3, handler: nil)
24 | }
25 |
26 | func testBasicPollingMessage() {
27 | let expect = expectation(description: "Basic polling test")
28 | socket.on("blankTest") {data, ack in
29 | expect.fulfill()
30 | }
31 |
32 | engine.parsePollingMessage("42[\"blankTest\"]")
33 | waitForExpectations(timeout: 3, handler: nil)
34 | }
35 |
36 | func testTwoPacketsInOnePollTest() {
37 | let finalExpectation = expectation(description: "Final packet in poll test")
38 | var gotBlank = false
39 |
40 | socket.on("blankTest") {data, ack in
41 | gotBlank = true
42 | }
43 |
44 | socket.on("stringTest") {data, ack in
45 | if let str = data[0] as? String, gotBlank {
46 | if str == "hello" {
47 | finalExpectation.fulfill()
48 | }
49 | }
50 | }
51 |
52 | engine.parsePollingMessage("42[\"blankTest\"]\u{1e}42[\"stringTest\",\"hello\"]")
53 | waitForExpectations(timeout: 3, handler: nil)
54 | }
55 |
56 | func testEngineDoesErrorOnUnknownTransport() {
57 | let finalExpectation = expectation(description: "Unknown Transport")
58 |
59 | socket.on("error") {data, ack in
60 | if let error = data[0] as? String, error == "Unknown transport" {
61 | finalExpectation.fulfill()
62 | }
63 | }
64 |
65 | engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}")
66 | waitForExpectations(timeout: 3, handler: nil)
67 | }
68 |
69 | func testEngineDoesErrorOnUnknownMessage() {
70 | let finalExpectation = expectation(description: "Engine Errors")
71 |
72 | socket.on("error") {data, ack in
73 | finalExpectation.fulfill()
74 | }
75 |
76 | engine.parseEngineMessage("afafafda")
77 | waitForExpectations(timeout: 3, handler: nil)
78 | }
79 |
80 | func testEngineDecodesUTF8Properly() {
81 | let expect = expectation(description: "Engine Decodes utf8")
82 |
83 | socket.on("stringTest") {data, ack in
84 | XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo𦅙𦅛", "Failed string test")
85 | expect.fulfill()
86 | }
87 |
88 | let stringMessage = "42[\"stringTest\",\"lïne one\\nlīne \\rtwo𦅙𦅛\"]"
89 |
90 | engine.parsePollingMessage("\(stringMessage)")
91 | waitForExpectations(timeout: 3, handler: nil)
92 | }
93 |
94 | func testEncodeURLProperly() {
95 | engine.connectParams = [
96 | "created": "2016-05-04T18:31:15+0200"
97 | ]
98 |
99 | XCTAssertEqual(engine.urlPolling.query, "transport=polling&b64=1&created=2016-05-04T18%3A31%3A15%2B0200&EIO=4")
100 | XCTAssertEqual(engine.urlWebSocket.query, "transport=websocket&created=2016-05-04T18%3A31%3A15%2B0200&EIO=4")
101 |
102 | engine.connectParams = [
103 | "forbidden": "!*'();:@&=+$,/?%#[]\" {}^|"
104 | ]
105 |
106 | 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%7C&EIO=4")
107 | 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%7C&EIO=4")
108 | }
109 |
110 | func testBase64Data() {
111 | let expect = expectation(description: "Engine Decodes base64 data")
112 | let b64String = "baGVsbG8NCg=="
113 | let packetString = "451-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]"
114 |
115 | socket.on("test") {data, ack in
116 | if let data = data[0] as? Data, let string = String(data: data, encoding: .utf8) {
117 | XCTAssertEqual(string, "hello")
118 | }
119 |
120 | expect.fulfill()
121 | }
122 |
123 | engine.parseEngineMessage(packetString)
124 | engine.parseEngineMessage(b64String)
125 |
126 | waitForExpectations(timeout: 3, handler: nil)
127 | }
128 |
129 | func testSettingExtraHeadersBeforeConnectSetsEngineExtraHeaders() {
130 | let newValue = ["hello": "world"]
131 |
132 | manager.engine = engine
133 | manager.setTestStatus(.notConnected)
134 | manager.config = [.extraHeaders(["new": "value"])]
135 | manager.config.insert(.extraHeaders(newValue), replacing: true)
136 |
137 | XCTAssertEqual(2, manager.config.count)
138 | XCTAssertEqual(manager.engine!.extraHeaders!, newValue)
139 |
140 | for config in manager.config {
141 | switch config {
142 | case let .extraHeaders(headers):
143 | XCTAssertTrue(headers.keys.contains("hello"), "It should contain hello header key")
144 | XCTAssertFalse(headers.keys.contains("new"), "It should not contain old data")
145 | case .path:
146 | continue
147 | default:
148 | XCTFail("It should only have two configs")
149 | }
150 | }
151 | }
152 |
153 | func testSettingExtraHeadersAfterConnectDoesNotIgnoreChanges() {
154 | let newValue = ["hello": "world"]
155 |
156 | manager.engine = engine
157 | manager.setTestStatus(.connected)
158 | engine.setConnected(true)
159 | manager.config = [.extraHeaders(["new": "value"])]
160 | manager.config.insert(.extraHeaders(["hello": "world"]), replacing: true)
161 |
162 | XCTAssertEqual(2, manager.config.count)
163 | XCTAssertEqual(manager.engine!.extraHeaders!, newValue)
164 | }
165 |
166 | func testSettingPathAfterConnectDoesNotIgnoreChanges() {
167 | let newValue = "/newpath/"
168 |
169 | manager.engine = engine
170 | manager.setTestStatus(.connected)
171 | engine.setConnected(true)
172 | manager.config.insert(.path(newValue))
173 |
174 | XCTAssertEqual(1, manager.config.count)
175 | XCTAssertEqual(manager.engine!.socketPath, newValue)
176 | }
177 |
178 | func testSettingCompressAfterConnectDoesNotIgnoreChanges() {
179 | manager.engine = engine
180 | manager.setTestStatus(.connected)
181 | engine.setConnected(true)
182 | manager.config.insert(.compress)
183 |
184 | XCTAssertEqual(2, manager.config.count)
185 | XCTAssertTrue(manager.engine!.compress)
186 | }
187 |
188 | func testSettingForcePollingAfterConnectDoesNotIgnoreChanges() {
189 | manager.engine = engine
190 | manager.setTestStatus(.connected)
191 | engine.setConnected(true)
192 | manager.config.insert(.forcePolling(true))
193 |
194 | XCTAssertEqual(2, manager.config.count)
195 | XCTAssertTrue(manager.engine!.forcePolling)
196 | }
197 |
198 | func testSettingForceWebSocketsAfterConnectDoesNotIgnoreChanges() {
199 | manager.engine = engine
200 | manager.setTestStatus(.connected)
201 | engine.setConnected(true)
202 | manager.config.insert(.forceWebsockets(true))
203 |
204 | XCTAssertEqual(2, manager.config.count)
205 | XCTAssertTrue(manager.engine!.forceWebsockets)
206 | }
207 |
208 | func testChangingEngineHeadersAfterInit() {
209 | engine.extraHeaders = ["Hello": "World"]
210 |
211 | let req = engine.createRequestForPostWithPostWait()
212 |
213 | XCTAssertEqual("World", req.allHTTPHeaderFields?["Hello"])
214 | }
215 |
216 | var manager: SocketManager!
217 | var socket: SocketIOClient!
218 | var engine: SocketEngine!
219 |
220 | override func setUp() {
221 | super.setUp()
222 |
223 | manager = SocketManager(socketURL: URL(string: "http://localhost")!)
224 | socket = manager.defaultSocket
225 | engine = SocketEngine(client: manager, url: URL(string: "http://localhost")!, options: nil)
226 |
227 | socket.setTestable()
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/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.reconnectWaitMax, 30)
19 | XCTAssertEqual(manager.randomizationFactor, 0.5)
20 | XCTAssertEqual(manager.status, .notConnected)
21 | }
22 |
23 | func testSettingConfig() {
24 | let manager = SocketManager(socketURL: URL(string: "https://example.com/")!)
25 |
26 | XCTAssertEqual(manager.config.first!, .secure(true))
27 |
28 | manager.config = []
29 |
30 | XCTAssertEqual(manager.config.first!, .secure(true))
31 | }
32 |
33 | func testBackoffIntervalCalulation() {
34 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWaitMax))
35 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 0), 15)
36 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 1), 22.5)
37 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 2), 33.75)
38 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 50), Double(manager.reconnectWaitMax))
39 | XCTAssertLessThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWaitMax))
40 |
41 | XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: -1), Double(manager.reconnectWait))
42 | XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 0), Double(manager.reconnectWait))
43 | XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 1), 15)
44 | XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 2), 22.5)
45 | XCTAssertGreaterThanOrEqual(manager.reconnectInterval(attempts: 10000), Double(manager.reconnectWait))
46 | }
47 |
48 | func testManagerCallsConnect() {
49 | setUpSockets()
50 |
51 | socket.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the default socket")
52 | socket2.expectations[ManagerExpectation.didConnectCalled] = expectation(description: "The manager should call connect on the socket")
53 |
54 | socket.connect()
55 | socket2.connect()
56 |
57 | manager.fakeConnecting()
58 | manager.fakeConnecting(toNamespace: "/swift")
59 |
60 | waitForExpectations(timeout: 0.3)
61 | }
62 |
63 | func testManagerDoesNotCallConnectWhenConnectingWithLessThanOneReconnect() {
64 | setUpSockets()
65 |
66 | let expect = expectation(description: "The manager should not call connect on the engine")
67 | expect.isInverted = true
68 |
69 | let engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
70 |
71 | engine.onConnect = {
72 | expect.fulfill()
73 | }
74 | manager.setTestStatus(.connecting)
75 | manager.setCurrentReconnect(currentReconnect: 0)
76 | manager.engine = engine
77 |
78 | manager.connect()
79 |
80 | waitForExpectations(timeout: 0.3)
81 | }
82 |
83 | func testManagerCallConnectWhenConnectingAndMoreThanOneReconnect() {
84 | setUpSockets()
85 |
86 | let expect = expectation(description: "The manager should call connect on the engine")
87 | let engine = TestEngine(client: manager, url: manager.socketURL, options: nil)
88 |
89 | engine.onConnect = {
90 | expect.fulfill()
91 | }
92 | manager.setTestStatus(.connecting)
93 | manager.setCurrentReconnect(currentReconnect: 1)
94 | manager.engine = engine
95 |
96 | manager.connect()
97 |
98 | waitForExpectations(timeout: 0.8)
99 | }
100 |
101 | func testManagerCallsDisconnect() {
102 | setUpSockets()
103 |
104 | socket.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the default socket")
105 | socket2.expectations[ManagerExpectation.didDisconnectCalled] = expectation(description: "The manager should call disconnect on the socket")
106 |
107 | socket2.on(clientEvent: .connect) {data, ack in
108 | self.manager.disconnect()
109 | self.manager.fakeDisconnecting()
110 | }
111 |
112 | socket.connect()
113 | socket2.connect()
114 |
115 | manager.fakeConnecting()
116 | manager.fakeConnecting(toNamespace: "/swift")
117 |
118 | waitForExpectations(timeout: 0.3)
119 | }
120 |
121 | // func testManagerEmitAll() {
122 | // setUpSockets()
123 | //
124 | // socket.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the default socket")
125 | // socket2.expectations[ManagerExpectation.emitAllEventCalled] = expectation(description: "The manager should emit an event to the socket")
126 | //
127 | // socket2.on(clientEvent: .connect) {data, ack in
128 | // print("connect")
129 | // self.manager.emitAll("event", "testing")
130 | // }
131 | //
132 | // socket.connect()
133 | // socket2.connect()
134 | //
135 | // manager.fakeConnecting(toNamespace: "/swift")
136 | //
137 | // waitForExpectations(timeout: 0.3)
138 | // }
139 |
140 | func testManagerSetsConfigs() {
141 | let queue = DispatchQueue(label: "testQueue")
142 |
143 | manager = TestManager(socketURL: URL(string: "http://localhost/")!, config: [
144 | .handleQueue(queue),
145 | .forceNew(true),
146 | .reconnects(false),
147 | .reconnectWait(5),
148 | .reconnectWaitMax(5),
149 | .randomizationFactor(0.7),
150 | .reconnectAttempts(5)
151 | ])
152 |
153 | XCTAssertEqual(manager.handleQueue, queue)
154 | XCTAssertTrue(manager.forceNew)
155 | XCTAssertFalse(manager.reconnects)
156 | XCTAssertEqual(manager.reconnectWait, 5)
157 | XCTAssertEqual(manager.reconnectWaitMax, 5)
158 | XCTAssertEqual(manager.randomizationFactor, 0.7)
159 | XCTAssertEqual(manager.reconnectAttempts, 5)
160 | }
161 |
162 | func testManagerRemovesSocket() {
163 | setUpSockets()
164 |
165 | manager.removeSocket(socket)
166 |
167 | XCTAssertNil(manager.nsps[socket.nsp])
168 | }
169 |
170 | private func setUpSockets() {
171 | socket = manager.testSocket(forNamespace: "/")
172 | socket2 = manager.testSocket(forNamespace: "/swift")
173 | }
174 |
175 | private var manager: TestManager!
176 | private var socket: TestSocket!
177 | private var socket2: TestSocket!
178 |
179 | override func setUp() {
180 | super.setUp()
181 |
182 | manager = TestManager(socketURL: URL(string: "http://localhost/")!, config: [.log(false)])
183 | socket = nil
184 | socket2 = nil
185 | }
186 | }
187 |
188 | public enum ManagerExpectation: String {
189 | case didConnectCalled
190 | case didDisconnectCalled
191 | case emitAllEventCalled
192 | }
193 |
194 | public class TestManager: SocketManager {
195 | public func setCurrentReconnect(currentReconnect: Int) {
196 | self.currentReconnectAttempt = currentReconnect
197 | }
198 |
199 | public override func disconnect() {
200 | setTestStatus(.disconnected)
201 | }
202 |
203 | public func testSocket(forNamespace nsp: String) -> TestSocket {
204 | return socket(forNamespace: nsp) as! TestSocket
205 | }
206 |
207 | public func fakeDisconnecting() {
208 | engineDidClose(reason: "")
209 | }
210 |
211 | public func fakeConnecting(toNamespace nsp: String = "/") {
212 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
213 | // Fake connecting
214 | self.parseEngineMessage("0\(nsp)")
215 | }
216 | }
217 |
218 | public override func socket(forNamespace nsp: String) -> SocketIOClient {
219 | // set socket to our test socket, the superclass method will get this from nsps
220 | nsps[nsp] = TestSocket(manager: self, nsp: nsp)
221 |
222 | return super.socket(forNamespace: nsp)
223 | }
224 | }
225 |
226 | public class TestSocket: SocketIOClient {
227 | public var expectations = [ManagerExpectation: XCTestExpectation]()
228 |
229 | public override func didConnect(toNamespace nsp: String, payload: [String: Any]?) {
230 | expectations[ManagerExpectation.didConnectCalled]?.fulfill()
231 | expectations[ManagerExpectation.didConnectCalled] = nil
232 |
233 | super.didConnect(toNamespace: nsp, payload: payload)
234 | }
235 |
236 | public override func didDisconnect(reason: String) {
237 | expectations[ManagerExpectation.didDisconnectCalled]?.fulfill()
238 | expectations[ManagerExpectation.didDisconnectCalled] = nil
239 |
240 | super.didDisconnect(reason: reason)
241 | }
242 |
243 | public override func emit(_ event: String, _ items: SocketData..., completion: (() -> ())? = nil) {
244 | expectations[ManagerExpectation.emitAllEventCalled]?.fulfill()
245 | expectations[ManagerExpectation.emitAllEventCalled] = nil
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/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/TestSocketIO/utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Erik Little on 2019-01-11.
3 | //
4 |
5 | import Foundation
6 | @testable import SocketIO
7 |
8 | public class OBjcUtils: NSObject {
9 | @objc
10 | public static func setTestStatus(socket: SocketIOClient, status: SocketIOStatus) {
11 | socket.setTestStatus(status)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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/15to16.md:
--------------------------------------------------------------------------------
1 | # Upgrading from v15 to v16
2 |
3 | This guide will help you navigate the changes that were introduced in v16.
4 |
5 | ## Objective-c is no longer supported. You must now use Swift.
6 |
7 | ## Client supports multiple socket.io versions
8 |
9 | The client now supports socket.io 3 servers. This is mostly a transparent change, however if your server
10 | is socket.io 2, you must send `.version(.two)` as an option to the manager.
11 |
12 | ```swift
13 | SocketManager(socketURL: URL(string:"http://localhost:8087/")!, config: [.version(.two)])
14 | ```
15 |
16 | ## How to upgrade
17 |
18 | - first, upgrade the Socket.IO server to v4 with the compatibility mode enabled (`allowEIO3: true`)
19 | - then, upgrade the clients to v16
20 | - finally, once all clients have upgraded, disable the compatibility mode
21 |
22 | You can check the version of the connection on the server side with:
23 |
24 | ```js
25 | io.on("connection", (socket) => {
26 | // either 3 for the 3rd revision of the protocol (Socket.IO v2) or 4 for the 4th revision (Socket.IO v3/v4)
27 | const version = socket.conn.protocol;
28 | });
29 | ```
30 |
31 | See also:
32 |
33 | - [Compatibility table](https://nuclearace.github.io/Socket.IO-Client-Swift/Compatibility.html)
34 | - Migrating from 2.x to 3.0: https://socket.io/docs/v4/migrating-from-2-x-to-3-0/
35 | - Migrating from 3.x to 4.0: https://socket.io/docs/v4/migrating-from-3-x-to-4-0/
36 |
--------------------------------------------------------------------------------
/Usage Docs/Compatibility.md:
--------------------------------------------------------------------------------
1 | Here is the compatibility table with the Node.js server:
2 |
3 |
4 |
5 |
Swift Client version
6 |
Socket.IO server version
7 |
8 |
9 |
2.x
10 |
3.x
11 |
4.x
12 |
13 |
14 |
v15.x
15 |
YES
16 |
YES1
17 |
YES2
18 |
19 |
20 |
v16.x
21 |
YES3
22 |
YES
23 |
YES
24 |
25 |
26 |
27 | [1] Yes, with allowEIO3: true (server) and `.connectParams(["EIO": "3"])` (client):
28 |
29 | *Server*
30 |
31 | ```js
32 | const { createServer } = require("http");
33 | const { Server } = require("socket.io");
34 |
35 | const httpServer = createServer();
36 | const io = new Server(httpServer, {
37 | allowEIO3: true
38 | });
39 |
40 | httpServer.listen(8080);
41 | ```
42 |
43 | *Client*
44 |
45 | ```swift
46 | SocketManager(socketURL: URL(string:"http://localhost:8080/")!, config: [.connectParams(["EIO": "3"])])
47 | ```
48 |
49 | [2] Yes, allowEIO3: true (server)
50 |
51 | [3] Yes, with `.version(.two)` (client):
52 |
53 | ```swift
54 | SocketManager(socketURL: URL(string:"http://localhost:8080/")!, config: [.version(.two)])
55 | ```
56 |
57 | See also:
58 |
59 | - Migrating from 2.x to 3.0: https://socket.io/docs/v4/migrating-from-2-x-to-3-0/
60 | - Migrating from 3.x to 4.0: https://socket.io/docs/v4/migrating-from-3-x-to-4-0/
61 | - Socket.IO protocol: https://github.com/socketio/socket.io-protocol
62 |
--------------------------------------------------------------------------------
/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/15to16.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 15to16 Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
This guide will help you navigate the changes that were introduced in v16.
235 |
Objective-c is no longer supported. You must now use Swift.
236 |
Client supports multiple socket.io versions
237 |
238 |
The client now supports socket.io 3 servers. This is mostly a transparent change, however if your sever
239 | is socket.io 2, you must send .version(.two) as an option to the manager.