Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4534f9ca29 | |||
| 5527c29d21 | |||
| bc6c868641 | |||
| 9b942131d5 | |||
| 108aa5e4c8 | |||
| 0b10d3e21e | |||
| a109616d95 | |||
| 5f6b4d235f | |||
| c72659bd32 | |||
| c0123b373c | |||
| ebdc260ea6 | |||
| 361a905d30 | |||
| 0f18ae5959 | |||
| 82a01218cc | |||
| 46d8cacb2e | |||
| 2280423c21 | |||
| 2022e6f553 | |||
| b0729b1181 | |||
| fd8a3fc3fa | |||
| f58389b4ff | |||
| 4b28929ae5 | |||
| 384b8039ef | |||
| c70d18f03d | |||
| 813d92d917 | |||
| c6dfbf1ccb | |||
| e22724f5cf | |||
| dceb95c4ea | |||
| c3b05cde30 | |||
| d14d4af2e7 | |||
| ed6eee4155 | |||
| 79cd8a2a89 | |||
| b59d3e1512 | |||
| a403aea07e | |||
| 69a0180e2c | |||
| 12cdf2bce2 | |||
| 70fd033db6 | |||
| d2bf71599a | |||
| 09297799cc | |||
| b111e1a8f0 | |||
| 451480d86f | |||
| 9811225d21 | |||
| cfd37a6f08 | |||
| c89a127289 | |||
| 6a2a3a7c36 | |||
| 8266c80b85 | |||
| d8278d951a | |||
| 0f2ee9f68e | |||
| 8b9c52eec3 | |||
| 073aa863cd | |||
| cd185b6fe9 | |||
| 114e5df9b6 | |||
| 430bfc32c3 | |||
| 1edb4c0b65 | |||
| 157291cb5e | |||
| 31f522155a | |||
| 327a7d8e9e | |||
| 6e10c04c83 | |||
| 97401e04bc | |||
| 6ba3563148 | |||
| 146368c71a | |||
| 4ea6f4be3a | |||
| 6cb1c474e0 | |||
| 7102d31afb | |||
| 0f94bb3d95 | |||
| 28070bf2a4 | |||
| ee0c7f9213 | |||
| a49c7e2119 | |||
| 7fad6b3a82 | |||
| 06baac0fe7 | |||
| 5866676617 | |||
| b074c6c833 | |||
| 8f2af31bd3 | |||
| d3fb769f93 | |||
| f58eb4ccad | |||
| 880b5b4c9e | |||
| 97e378d94a | |||
| ca20b02ca2 | |||
| a1f133d737 | |||
| 13f685f464 | |||
| fae7ef2817 | |||
| b24eaba135 | |||
| e8182190b7 | |||
| bf24825167 | |||
| 9ee57e089d | |||
| f057a8b33d | |||
| b8fe0f64e3 | |||
| 94e54af61c | |||
| 44ce58956f | |||
| c7cc1db0e3 | |||
| a438ea1977 |
+93
-2
@@ -4,7 +4,7 @@
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control?
|
||||
#
|
||||
# Pods/
|
||||
Pods/
|
||||
|
||||
# Xcode
|
||||
.DS_Store
|
||||
@@ -24,4 +24,95 @@ DerivedData
|
||||
.idea/
|
||||
*.hmap
|
||||
*.xccheckout
|
||||
*.xcodeproj/*.xcworkspace
|
||||
*.xcodeproj/*.xcworkspace
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/swift,swiftpm
|
||||
|
||||
### Swift ###
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## Build generated
|
||||
build/
|
||||
DerivedData/
|
||||
|
||||
## Various settings
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata/
|
||||
|
||||
## Other
|
||||
*.moved-aside
|
||||
*.xccheckout
|
||||
*.xcscmblueprint
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
#
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
### SwiftPM ###
|
||||
Packages
|
||||
xcuserdata
|
||||
*.xcodeproj
|
||||
|
||||
|
||||
# End of https://www.gitignore.io/api/swift,swiftpm
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
4.0
|
||||
@@ -0,0 +1,7 @@
|
||||
osx_image: xcode10
|
||||
language: objective-c
|
||||
before_install:
|
||||
- gem install cocoapods --pre
|
||||
- gem cleanup
|
||||
script:
|
||||
- ./build.sh
|
||||
@@ -2,6 +2,81 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
#### [3.0.6](https://github.com/daltoniam/Starscream/tree/3.0.6)
|
||||
|
||||
* Swift 4.2 and Xcode 10 support
|
||||
* added pongDelegate
|
||||
* moved CommonCrypto and zlib dependencies
|
||||
* HTTP proxy support
|
||||
|
||||
#### [3.0.5](https://github.com/daltoniam/Starscream/tree/3.0.5)
|
||||
|
||||
Swift 4.1 support and bug fixes.
|
||||
|
||||
Pull Requests:
|
||||
[#492](https://github.com/daltoniam/Starscream/pull/492)
|
||||
[#461](https://github.com/daltoniam/Starscream/pull/461)
|
||||
[#476](https://github.com/daltoniam/Starscream/pull/476)
|
||||
|
||||
Issues:
|
||||
[#494](https://github.com/daltoniam/Starscream/issues/494)
|
||||
[#491](https://github.com/daltoniam/Starscream/issues/491)
|
||||
[#474](https://github.com/daltoniam/Starscream/issues/474)
|
||||
[#471](https://github.com/daltoniam/Starscream/issues/471)
|
||||
[#437](https://github.com/daltoniam/Starscream/issues/437)
|
||||
[#445](https://github.com/daltoniam/Starscream/issues/445)
|
||||
[#466](https://github.com/daltoniam/Starscream/issues/466)
|
||||
|
||||
|
||||
#### [3.0.4](https://github.com/daltoniam/Starscream/tree/3.0.4)
|
||||
|
||||
Improved error handling. Timeout fix. Small assorted fixes.
|
||||
|
||||
Pull Requests:
|
||||
[#452](https://github.com/daltoniam/Starscream/pull/452)
|
||||
[#448](https://github.com/daltoniam/Starscream/pull/448)
|
||||
[#444](https://github.com/daltoniam/Starscream/pull/444)
|
||||
[#443](https://github.com/daltoniam/Starscream/pull/443)
|
||||
|
||||
Issues:
|
||||
[#415](https://github.com/daltoniam/Starscream/issues/415)
|
||||
[#422](https://github.com/daltoniam/Starscream/issues/422)
|
||||
[#429](https://github.com/daltoniam/Starscream/issues/429)
|
||||
[#433](https://github.com/daltoniam/Starscream/issues/433)
|
||||
[#439](https://github.com/daltoniam/Starscream/issues/439)
|
||||
|
||||
#### [3.0.3](https://github.com/daltoniam/Starscream/tree/3.0.3)
|
||||
|
||||
Assorted fixes.
|
||||
|
||||
Pull Requests:
|
||||
[#438](https://github.com/daltoniam/Starscream/pull/438)
|
||||
[#423](https://github.com/daltoniam/Starscream/pull/423)
|
||||
[#420](https://github.com/daltoniam/Starscream/pull/420)
|
||||
[#418](https://github.com/daltoniam/Starscream/pull/418)
|
||||
[#410](https://github.com/daltoniam/Starscream/pull/410)
|
||||
[#405](https://github.com/daltoniam/Starscream/pull/405)
|
||||
[#404](https://github.com/daltoniam/Starscream/pull/404)
|
||||
[#400](https://github.com/daltoniam/Starscream/pull/400)
|
||||
|
||||
Issues:
|
||||
[#435](https://github.com/daltoniam/Starscream/issues/435)
|
||||
[#431](https://github.com/daltoniam/Starscream/issues/431)
|
||||
[#426](https://github.com/daltoniam/Starscream/issues/426)
|
||||
[#409](https://github.com/daltoniam/Starscream/issues/409)
|
||||
[#408](https://github.com/daltoniam/Starscream/issues/408)
|
||||
[#401](https://github.com/daltoniam/Starscream/issues/401)
|
||||
[#399](https://github.com/daltoniam/Starscream/issues/399)
|
||||
[#378](https://github.com/daltoniam/Starscream/issues/378)
|
||||
|
||||
#### [3.0.2](https://github.com/daltoniam/Starscream/tree/3.0.2)
|
||||
|
||||
Small fixes for 3.0.1.
|
||||
|
||||
[#394](https://github.com/daltoniam/Starscream/issues/394)
|
||||
[#392](https://github.com/daltoniam/Starscream/issues/392)
|
||||
[#391](https://github.com/daltoniam/Starscream/issues/391)
|
||||
|
||||
#### [3.0.1](https://github.com/daltoniam/Starscream/tree/3.0.1)
|
||||
|
||||
Small fixes for 3.0.0.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
github "daltoniam/zlib-spm" ~> 1.1
|
||||
github "daltoniam/common-crypto-spm" ~> 1.1
|
||||
@@ -1,2 +0,0 @@
|
||||
github "daltoniam/zlib-spm" "1.1"
|
||||
github "daltoniam/common-crypto-spm" "1.1"
|
||||
@@ -0,0 +1,4 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
gem "cocoapods", ">= 1.6.0.beta.1"
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.0)
|
||||
activesupport (4.2.10)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
atomos (0.1.3)
|
||||
babosa (1.0.2)
|
||||
claide (1.0.2)
|
||||
cocoapods (1.6.0.beta.1)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.6.0.beta.1)
|
||||
cocoapods-deintegrate (>= 1.0.2, < 2.0)
|
||||
cocoapods-downloader (>= 1.2.1, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.3.1, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (~> 2.0.1)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.6)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.2)
|
||||
xcodeproj (>= 1.6.0, < 2.0)
|
||||
cocoapods-core (1.6.0.beta.1)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.2)
|
||||
cocoapods-downloader (1.2.1)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.0.0)
|
||||
cocoapods-trunk (1.3.1)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
declarative (0.0.10)
|
||||
declarative-option (0.1.0)
|
||||
domain_name (0.5.20180417)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.5.0)
|
||||
emoji_regex (0.1.1)
|
||||
escape (0.0.4)
|
||||
excon (0.62.0)
|
||||
faraday (0.15.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (0.12.2)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
fastimage (2.1.4)
|
||||
fastlane (2.105.2)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
babosa (>= 1.0.2, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 2.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (~> 0.1)
|
||||
excon (>= 0.45.0, < 1.0.0)
|
||||
faraday (~> 0.9)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 0.9)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.21.2, < 0.24.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
mini_magick (~> 4.5.1)
|
||||
multi_json
|
||||
multi_xml (~> 0.5)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
public_suffix (~> 2.0.0)
|
||||
rubyzip (>= 1.2.2, < 2.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 1.6.2, < 2.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.6.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fourflusher (2.0.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.23.9)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mime-types (~> 3.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.9)
|
||||
googleauth (0.6.6)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.12)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.1.0)
|
||||
jwt (2.1.0)
|
||||
memoist (0.16.0)
|
||||
mime-types (3.2.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2018.0812)
|
||||
mini_magick (4.5.1)
|
||||
minitest (5.11.3)
|
||||
molinillo (0.6.6)
|
||||
multi_json (1.13.1)
|
||||
multi_xml (0.6.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.2.6)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.0)
|
||||
netrc (0.11.0)
|
||||
os (1.0.0)
|
||||
plist (3.4.0)
|
||||
public_suffix (2.0.5)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (1.3.1)
|
||||
rubyzip (1.2.2)
|
||||
security (0.1.3)
|
||||
signet (0.10.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.5)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (1.8.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
tty-cursor (0.6.0)
|
||||
tty-screen (0.6.5)
|
||||
tty-spinner (0.8.0)
|
||||
tty-cursor (>= 0.5.0)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.4.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.6.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (>= 1.6.0.beta.1)
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
+9
-7
@@ -1,3 +1,5 @@
|
||||
// swift-tools-version:4.2
|
||||
|
||||
//
|
||||
// Package.Swift
|
||||
// Starscream
|
||||
@@ -21,12 +23,12 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Starscream",
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/daltoniam/zlib-spm.git",
|
||||
majorVersion: 1, minor: 1),
|
||||
.Package(url: "https://github.com/daltoniam/common-crypto-spm",
|
||||
majorVersion: 1, minor: 1),
|
||||
name: "Starscream",
|
||||
products: [
|
||||
.library(name: "Starscream", targets: ["Starscream"])
|
||||
],
|
||||
exclude: ["Tests", "examples"]
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(name: "Starscream")
|
||||
]
|
||||
)
|
||||
|
||||
@@ -133,6 +133,25 @@ The writePing method is the same as write, but sends a ping control frame.
|
||||
socket.write(ping: Data()) //example on how to write a ping control frame over the socket!
|
||||
```
|
||||
|
||||
### write a pong frame
|
||||
|
||||
|
||||
the writePong method is the same as writePing, but sends a pong control frame.
|
||||
|
||||
```swift
|
||||
socket.write(pong: Data()) //example on how to write a pong control frame over the socket!
|
||||
```
|
||||
|
||||
Starscream will automatically respond to incoming `ping` control frames so you do not need to manually send `pong`s.
|
||||
|
||||
However if for some reason you need to control this process you can turn off the automatic `ping` response by disabling `respondToPingWithPong`.
|
||||
|
||||
```swift
|
||||
socket.respondToPingWithPong = false //Do not automaticaly respond to incoming pings with pongs.
|
||||
```
|
||||
|
||||
In most cases you will not need to do this.
|
||||
|
||||
### disconnect
|
||||
|
||||
The disconnect method does what you would expect and closes the socket.
|
||||
@@ -141,6 +160,12 @@ The disconnect method does what you would expect and closes the socket.
|
||||
socket.disconnect()
|
||||
```
|
||||
|
||||
The socket can be forcefully closed, by specifying a timeout (in milliseconds). A timeout of zero will also close the socket immediately without waiting on the server.
|
||||
|
||||
```swift
|
||||
socket.disconnect(forceTimeout: 10, closeCode: CloseCode.normal.rawValue)
|
||||
```
|
||||
|
||||
### isConnected
|
||||
|
||||
Returns if the socket is connected or not.
|
||||
@@ -261,7 +286,7 @@ To use Starscream in your project add the following 'Podfile' to your project
|
||||
platform :ios, '9.0'
|
||||
use_frameworks!
|
||||
|
||||
pod 'Starscream', '~> 3.0.0'
|
||||
pod 'Starscream', '~> 3.0.2'
|
||||
|
||||
Then run:
|
||||
|
||||
@@ -283,7 +308,7 @@ $ brew install carthage
|
||||
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
|
||||
|
||||
```
|
||||
github "daltoniam/Starscream" >= 3.0.0
|
||||
github "daltoniam/Starscream" >= 3.0.2
|
||||
```
|
||||
|
||||
### Rogue
|
||||
@@ -332,7 +357,7 @@ In most cases you do not need the extra info and should use the normal delegate.
|
||||
|
||||
#### websocketDidReceiveMessage
|
||||
```swift
|
||||
func websocketDidReceiveMessage(socket: WebSocketClient, text: String, response: WebSocket.WSResponse {
|
||||
func websocketDidReceiveMessage(socket: WebSocketClient, text: String, response: WebSocket.WSResponse) {
|
||||
print("got some text: \(text)")
|
||||
print("First frame for this message arrived on \(response.firstFrame)")
|
||||
}
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.1</string>
|
||||
<string>3.0.5</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import SSCZLib
|
||||
import zlib
|
||||
|
||||
class Decompressor {
|
||||
private var strm = z_stream()
|
||||
@@ -52,7 +52,7 @@ class Decompressor {
|
||||
|
||||
func reset() throws {
|
||||
teardownInflate()
|
||||
guard initInflate() else { throw NSError() }
|
||||
guard initInflate() else { throw WSError(type: .compressionError, message: "Error for decompressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func decompress(_ data: Data, finish: Bool) throws -> Data {
|
||||
@@ -92,7 +92,7 @@ class Decompressor {
|
||||
guard (res == Z_OK && strm.avail_out > 0)
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw NSError(domain: WebSocket.ErrorDomain, code: Int(InternalErrorCode.compressionError.rawValue), userInfo: nil)
|
||||
throw WSError(type: .compressionError, message: "Error on decompressing", code: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class Compressor {
|
||||
|
||||
func reset() throws {
|
||||
teardownDeflate()
|
||||
guard initDeflate() else { throw NSError() }
|
||||
guard initDeflate() else { throw WSError(type: .compressionError, message: "Error for compressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func compress(_ data: Data) throws -> Data {
|
||||
@@ -157,7 +157,7 @@ class Compressor {
|
||||
guard res == Z_OK && strm.avail_out > 0
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw NSError(domain: WebSocket.ErrorDomain, code: Int(InternalErrorCode.compressionError.rawValue), userInfo: nil)
|
||||
throw WSError(type: .compressionError, message: "Error on compressing", code: 0)
|
||||
}
|
||||
|
||||
compressed.removeLast(4)
|
||||
@@ -0,0 +1,165 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationStream.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/23/18.
|
||||
// Copyright (c) 2014-2018 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
|
||||
///
|
||||
open class FoundationStream: NSObject, WSStream, StreamDelegate {
|
||||
private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.foundationstream", attributes: [])
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
private let security: FoundationSecurity
|
||||
public weak var delegate: WSStreamDelegate?
|
||||
let BUFFER_MAX = 4096
|
||||
var isConnected = false
|
||||
|
||||
public var enableSOCKSProxy = false
|
||||
|
||||
public init(security: FoundationSecurity = FoundationSecurity()) {
|
||||
self.security = security
|
||||
}
|
||||
|
||||
public func connect(url: URL, port: Int, timeout: TimeInterval, useSSL: Bool, completion: @escaping ((Error?) -> Void)) {
|
||||
var readStream: Unmanaged<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
let h = url.host! as NSString
|
||||
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream)
|
||||
inputStream = readStream!.takeRetainedValue()
|
||||
outputStream = writeStream!.takeRetainedValue()
|
||||
|
||||
#if os(watchOS) //watchOS is unfortunately missing the kCFStream properties to make this work
|
||||
#else
|
||||
if enableSOCKSProxy {
|
||||
let proxyDict = CFNetworkCopySystemProxySettings()
|
||||
let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
|
||||
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
|
||||
CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
|
||||
CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
|
||||
}
|
||||
#endif
|
||||
|
||||
guard let inStream = inputStream, let outStream = outputStream else { return }
|
||||
inStream.delegate = self
|
||||
outStream.delegate = self
|
||||
|
||||
if useSSL, let error = security.configure(inputStream: inStream, outputStream: outStream) {
|
||||
completion(error)
|
||||
return
|
||||
}
|
||||
|
||||
CFReadStreamSetDispatchQueue(inStream, FoundationStream.sharedWorkQueue)
|
||||
CFWriteStreamSetDispatchQueue(outStream, FoundationStream.sharedWorkQueue)
|
||||
inStream.open()
|
||||
outStream.open()
|
||||
isConnected = true
|
||||
|
||||
var out = timeout// wait X seconds before giving up
|
||||
FoundationStream.sharedWorkQueue.async { [weak self] in
|
||||
while !outStream.hasSpaceAvailable {
|
||||
usleep(100) // wait until the socket is ready
|
||||
out -= 100
|
||||
if out < 0 {
|
||||
completion(WSError(type: .writeTimeoutError, message: "Timed out waiting for the socket to be ready for a write", code: 0))
|
||||
return
|
||||
} else if let error = outStream.streamError {
|
||||
completion(error)
|
||||
return // disconnectStream will be called.
|
||||
} else if self == nil {
|
||||
completion(WSError(type: .closeError, message: "socket object has been dereferenced", code: 0))
|
||||
return
|
||||
}
|
||||
}
|
||||
completion(nil) //success!
|
||||
}
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> Void)) {
|
||||
guard isConnected else {return} //don't write to a dead socket
|
||||
var written = 0
|
||||
let total = data.count
|
||||
while written < total {
|
||||
guard let outStream = outputStream else {
|
||||
completion(WSError(type: .outputStreamWriteError, message: "output stream had an error during write", code: 0))
|
||||
return
|
||||
}
|
||||
data.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
|
||||
guard let buffer = UnsafeBufferPointer(start: pointer, count: total).fromOffset(written).baseAddress else {
|
||||
completion(WSError(type: .outputStreamWriteError, message: "buffer error during write", code: 0))
|
||||
return
|
||||
}
|
||||
written += outStream.write(buffer, maxLength: total - written)
|
||||
if written < 0 {
|
||||
completion(WSError(type: .outputStreamWriteError, message: "buffer error during write", code: 0))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
/// read data from the stream.
|
||||
public func read() -> Data? {
|
||||
guard let stream = inputStream else {return nil}
|
||||
let buf = NSMutableData(capacity: BUFFER_MAX)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = stream.read(buffer, maxLength: BUFFER_MAX)
|
||||
if length < 1 {
|
||||
return nil
|
||||
}
|
||||
return Data(bytes: buffer, count: length)
|
||||
}
|
||||
|
||||
public func cleanup() {
|
||||
isConnected = false
|
||||
if let stream = inputStream {
|
||||
stream.delegate = nil
|
||||
CFReadStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
stream.delegate = nil
|
||||
CFWriteStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
outputStream = nil
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
public func isValidSSLCertificate() -> Bool {
|
||||
guard let outputStream = outputStream else {return false} //the stream is already invalid
|
||||
return security.checkTrust(outputStream: outputStream)
|
||||
}
|
||||
|
||||
/// MARK: - StreamDelegate
|
||||
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
if eventCode == .hasBytesAvailable {
|
||||
if aStream == inputStream {
|
||||
delegate?.newBytesInStream()
|
||||
}
|
||||
} else if eventCode == .errorOccurred {
|
||||
delegate?.streamDidError(error: aStream.streamError)
|
||||
} else if eventCode == .endEncountered {
|
||||
delegate?.streamDidError(error: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NetworkStream.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/23/18.
|
||||
// Copyright (c) 2014-2018 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
#if canImport(Network)
|
||||
import Network
|
||||
#endif
|
||||
|
||||
/// Implementation of the Network framework that was introduced in iOS 12/MacOS 10.14.
|
||||
/// This class will probably replace the Foundation one in the future, but because Foundation is battle-tested
|
||||
/// it will continue to be provided for backwards compatibility reasons.
|
||||
@available(iOS 12.0, *)
|
||||
@available(iOSApplicationExtension 12.0, tvOSApplicationExtension 12.0, OSXApplicationExtension 10.14, *)
|
||||
open class NetworkStream: WSStream {
|
||||
public weak var delegate: WSStreamDelegate?
|
||||
private var stream: NWConnection?
|
||||
private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.networkstream", attributes: [])
|
||||
private var readQueue = [Data]()
|
||||
var running = false
|
||||
var connected = false
|
||||
|
||||
//need security stuff
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
/// connect to the websocket server and start the read loop
|
||||
public func connect(url: URL, port: Int, timeout: TimeInterval, useSSL: Bool, completion: @escaping ((Error?) -> Void)) {
|
||||
let parameters: NWParameters = useSSL ? .tls : .tcp
|
||||
let conn = NWConnection(host: NWEndpoint.Host.name(url.host!, nil), port: NWEndpoint.Port(rawValue: UInt16(port))!, using: parameters)
|
||||
connected = false
|
||||
func doConnect(_ error: Error?) {
|
||||
if !connected {
|
||||
completion(error)
|
||||
connected = true
|
||||
running = true
|
||||
} else {
|
||||
running = false
|
||||
}
|
||||
}
|
||||
conn.stateUpdateHandler = { [weak self] (newState) in
|
||||
switch newState {
|
||||
case .ready:
|
||||
doConnect(nil)
|
||||
case .waiting:
|
||||
self?.delegate?.streamIsWaitingForConnectivity()
|
||||
case .cancelled:
|
||||
doConnect(nil)
|
||||
case .failed(let error):
|
||||
doConnect(error)
|
||||
self?.delegate?.streamDidError(error: error)
|
||||
case .setup, .preparing:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.viabilityUpdateHandler = { [weak self] (isViable) in
|
||||
self?.delegate?.streamPathViabilityUpdate(isViable: isViable)
|
||||
}
|
||||
|
||||
conn.betterPathUpdateHandler = { [weak self] (isBetter) in
|
||||
self?.delegate?.streamBetterPathUpdate(isBetter: isBetter)
|
||||
}
|
||||
|
||||
conn.start(queue: NetworkStream.sharedWorkQueue)
|
||||
stream = conn
|
||||
running = true
|
||||
readLoop()
|
||||
}
|
||||
|
||||
/// Write data over the socket to the websocket server
|
||||
/// From how I understand the documentation for send, we might need to optimize with queued writes.
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> Void)) {
|
||||
stream?.send(content: data, completion: .contentProcessed { (sendError) in
|
||||
completion(nil) //sendError
|
||||
})
|
||||
}
|
||||
|
||||
/// get hte latest message from the read queue
|
||||
public func read() -> Data? {
|
||||
return readQueue.removeFirst()
|
||||
}
|
||||
|
||||
/// stream isn't to be used anymore
|
||||
public func cleanup() {
|
||||
running = false
|
||||
stream?.cancel()
|
||||
}
|
||||
|
||||
public func isValidSSLCertificate() -> Bool {
|
||||
return false //TODO: SSL pinning for the network framework
|
||||
}
|
||||
|
||||
//continually read from the stream waiting for more content to process
|
||||
func readLoop() {
|
||||
if !running {
|
||||
return
|
||||
}
|
||||
stream?.receive(minimumIncompleteLength: 2, maximumLength: 4096, completion: {[weak self] (data, context, isComplete, error) in
|
||||
guard let s = self else {return}
|
||||
if let err = error {
|
||||
s.delegate?.streamDidError(error: err)
|
||||
return
|
||||
}
|
||||
if let data = data {
|
||||
s.readQueue.append(data)
|
||||
s.delegate?.newBytesInStream()
|
||||
}
|
||||
// I'm not sure why this is needed (might be a bug),
|
||||
// but this indicates the stream is "dead" and should be closed
|
||||
// even though we never got that state update
|
||||
if isComplete && data == nil, context == nil, error == nil {
|
||||
s.delegate?.streamDidError(error: nil)
|
||||
s.cleanup()
|
||||
return
|
||||
}
|
||||
s.readLoop()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// SSLClientCertificate.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Tomasz Trela on 08/03/2018.
|
||||
// Copyright © 2018 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct SSLClientCertificateError: LocalizedError {
|
||||
public var errorDescription: String?
|
||||
|
||||
init(errorDescription: String) {
|
||||
self.errorDescription = errorDescription
|
||||
}
|
||||
}
|
||||
|
||||
public class SSLClientCertificate {
|
||||
internal let streamSSLCertificates: NSArray
|
||||
|
||||
/**
|
||||
Convenience init.
|
||||
- parameter pkcs12Path: Path to pkcs12 file containing private key and X.509 ceritifacte (.p12)
|
||||
- parameter password: file password, see **kSecImportExportPassphrase**
|
||||
*/
|
||||
public convenience init(pkcs12Path: String, password: String) throws {
|
||||
let pkcs12Url = URL(fileURLWithPath: pkcs12Path)
|
||||
do {
|
||||
try self.init(pkcs12Url: pkcs12Url, password: password)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init. For more information, see SSLSetCertificate() in Security/SecureTransport.h.
|
||||
- parameter identity: SecIdentityRef, see **kCFStreamSSLCertificates**
|
||||
- parameter identityCertificate: CFArray of SecCertificateRefs, see **kCFStreamSSLCertificates**
|
||||
*/
|
||||
public init(identity: SecIdentity, identityCertificate: SecCertificate) {
|
||||
self.streamSSLCertificates = NSArray(objects: identity, identityCertificate)
|
||||
}
|
||||
|
||||
/**
|
||||
Convenience init.
|
||||
- parameter pkcs12Url: URL to pkcs12 file containing private key and X.509 ceritifacte (.p12)
|
||||
- parameter password: file password, see **kSecImportExportPassphrase**
|
||||
*/
|
||||
public convenience init(pkcs12Url: URL, password: String) throws {
|
||||
let importOptions = [kSecImportExportPassphrase as String : password] as CFDictionary
|
||||
do {
|
||||
try self.init(pkcs12Url: pkcs12Url, importOptions: importOptions)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init.
|
||||
- parameter pkcs12Url: URL to pkcs12 file containing private key and X.509 ceritifacte (.p12)
|
||||
- parameter importOptions: A dictionary containing import options. A
|
||||
kSecImportExportPassphrase entry is required at minimum. Only password-based
|
||||
PKCS12 blobs are currently supported. See **SecImportExport.h**
|
||||
*/
|
||||
public init(pkcs12Url: URL, importOptions: CFDictionary) throws {
|
||||
do {
|
||||
let pkcs12Data = try Data(contentsOf: pkcs12Url)
|
||||
var rawIdentitiesAndCertificates: CFArray?
|
||||
let pkcs12CFData: CFData = pkcs12Data as CFData
|
||||
let importStatus = SecPKCS12Import(pkcs12CFData, importOptions, &rawIdentitiesAndCertificates)
|
||||
|
||||
guard importStatus == errSecSuccess else {
|
||||
throw SSLClientCertificateError(errorDescription: "(Starscream) Error during 'SecPKCS12Import', see 'SecBase.h' - OSStatus: \(importStatus)")
|
||||
}
|
||||
guard let identitiyAndCertificate = (rawIdentitiesAndCertificates as? Array<Dictionary<String, Any>>)?.first else {
|
||||
throw SSLClientCertificateError(errorDescription: "(Starscream) Error - PKCS12 file is empty")
|
||||
}
|
||||
|
||||
let identity = identitiyAndCertificate[kSecImportItemIdentity as String] as! SecIdentity
|
||||
var identityCertificate: SecCertificate?
|
||||
let copyStatus = SecIdentityCopyCertificate(identity, &identityCertificate)
|
||||
guard copyStatus == errSecSuccess else {
|
||||
throw SSLClientCertificateError(errorDescription: "(Starscream) Error during 'SecIdentityCopyCertificate', see 'SecBase.h' - OSStatus: \(copyStatus)")
|
||||
}
|
||||
self.streamSSLCertificates = NSArray(objects: identity, identityCertificate!)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 5/16/15.
|
||||
// Copyright (c) 2014-2016 Dalton Cherry.
|
||||
// Copyright (c) 2014-2018 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -24,10 +24,115 @@
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public protocol SSLTrustValidator {
|
||||
public protocol FoundationSSLValidator {
|
||||
func checkTrust(outputStream: OutputStream) -> Bool
|
||||
func configure(inputStream: InputStream, outputStream: OutputStream) -> WSError?
|
||||
}
|
||||
|
||||
open class FoundationSecurity: FoundationSSLValidator {
|
||||
public var pinner: SSLPinnerTrustValidator?
|
||||
public var desiredTrustHostname: String?
|
||||
public var disableCertValidation: Bool
|
||||
public var disableSSL = false
|
||||
public var overrideTrustHostname = false
|
||||
public var cipherSuites: [SSLCipherSuite]?
|
||||
public var clientCertificate: SSLClientCertificate?
|
||||
|
||||
/**
|
||||
Designated init for FoundationSecurity
|
||||
|
||||
- parameter sslPinner: is SSL pinning class you want to use
|
||||
- parameter desiredTrustHostname: is SSL hostname you want to validate
|
||||
- parameter disableCertValidation: is to disable the SSL certificate chain validation
|
||||
- parameter clientCertificate: is to configure client side authentication
|
||||
|
||||
- returns: a security object that is used with the FoundationStream
|
||||
*/
|
||||
public init(pinner: SSLPinnerTrustValidator? = nil, desiredTrustHostname: String? = nil, disableCertValidation: Bool = false, clientCertificate: SSLClientCertificate? = nil) {
|
||||
self.pinner = pinner
|
||||
self.desiredTrustHostname = desiredTrustHostname
|
||||
self.disableCertValidation = disableCertValidation
|
||||
self.self.clientCertificate = clientCertificate
|
||||
if desiredTrustHostname != nil {
|
||||
self.overrideTrustHostname = true
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(inputStream: InputStream, outputStream: OutputStream) -> WSError? {
|
||||
if disableSSL {
|
||||
return nil
|
||||
}
|
||||
inputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
outputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
#if os(watchOS) //watchOS is unfortunately missing the kCFStream properties to make this work
|
||||
#else
|
||||
var settings = [NSObject: NSObject]()
|
||||
if disableCertValidation {
|
||||
settings[kCFStreamSSLValidatesCertificateChain] = NSNumber(value: false)
|
||||
}
|
||||
if let hostname = desiredTrustHostname {
|
||||
settings[kCFStreamSSLPeerName] = hostname as NSString
|
||||
} else if overrideTrustHostname {
|
||||
settings[kCFStreamSSLPeerName] = kCFNull
|
||||
}
|
||||
if let clientCertificate = clientCertificate {
|
||||
settings[kCFStreamSSLCertificates] = clientCertificate.streamSSLCertificates
|
||||
}
|
||||
|
||||
inputStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
outputStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
#endif
|
||||
|
||||
if let cipherSuites = cipherSuites {
|
||||
#if os(watchOS) //watchOS is unfortunately missing the kCFStream properties to make this work
|
||||
#else
|
||||
if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?,
|
||||
let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
|
||||
let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count)
|
||||
let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count)
|
||||
if resIn != errSecSuccess {
|
||||
return WSError(type: .invalidSSLError, message: "Error setting incoming cypher suites", code: UInt16(resIn))
|
||||
} else if resOut != errSecSuccess {
|
||||
return WSError(type: .invalidSSLError, message: "Error setting outgoing cypher suites", code: UInt16(resOut))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func checkTrust(outputStream: OutputStream) -> Bool {
|
||||
#if os(watchOS) //watchOS is unfortunately missing the kCFStream properties to make this work
|
||||
return true
|
||||
#else
|
||||
guard let pinner = pinner else {return true} //true because we don't validated.
|
||||
|
||||
let trust = outputStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
|
||||
var domain = outputStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as! String?
|
||||
if domain == nil, let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
|
||||
var peerNameLen: Int = 0
|
||||
SSLGetPeerDomainNameLength(sslContextOut, &peerNameLen)
|
||||
var peerName = Data(count: peerNameLen)
|
||||
let _ = peerName.withUnsafeMutableBytes { (peerNamePtr: UnsafeMutablePointer<Int8>) in
|
||||
SSLGetPeerDomainName(sslContextOut, peerNamePtr, &peerNameLen)
|
||||
}
|
||||
if let peerDomain = String(bytes: peerName, encoding: .utf8), peerDomain.count > 0 {
|
||||
domain = peerDomain
|
||||
}
|
||||
}
|
||||
guard let t = trust else {return true} //true because this probably isn't and SSL stream without a trust.
|
||||
return pinner.isValid(t, domain: domain)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// SSLPinnerTrustValidator is the base protocol needed to do SSL pinning on the FoundationStream.
|
||||
public protocol SSLPinnerTrustValidator {
|
||||
func isValid(_ trust: SecTrust, domain: String?) -> Bool
|
||||
}
|
||||
|
||||
|
||||
/// SSLCert the object to hold and use an SSL key or certificate.
|
||||
open class SSLCert {
|
||||
var certData: Data?
|
||||
var key: SecKey?
|
||||
@@ -55,9 +160,12 @@ open class SSLCert {
|
||||
}
|
||||
}
|
||||
|
||||
open class SSLSecurity : SSLTrustValidator {
|
||||
/// FoundationPinner is a very simple SSL pinning implementation.
|
||||
/// Also See TrustKit...more words.
|
||||
open class FoundationPinner : SSLPinnerTrustValidator {
|
||||
public var validatedDN = true //should the domain name be validated?
|
||||
|
||||
public var validateEntireChain = true //should the entire cert chain be validated
|
||||
|
||||
var isReady = false //is the key processing done?
|
||||
var certificates: [Data]? //the certificates
|
||||
var pubKeys: [SecKey]? //the public keys
|
||||
@@ -132,7 +240,7 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
|
||||
- returns: if the key was successfully validated
|
||||
*/
|
||||
public func isValid(_ trust: SecTrust, domain: String?) -> Bool {
|
||||
open func isValid(_ trust: SecTrust, domain: String?) -> Bool {
|
||||
|
||||
var tries = 0
|
||||
while !self.isReady {
|
||||
@@ -170,6 +278,9 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust,&result)
|
||||
if result == .unspecified || result == .proceed {
|
||||
if !validateEntireChain {
|
||||
return true
|
||||
}
|
||||
var trustedCount = 0
|
||||
for serverCert in serverCerts {
|
||||
for cert in certs {
|
||||
@@ -194,7 +305,7 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ data: Data) -> SecKey? {
|
||||
public func extractPublicKey(_ data: Data) -> SecKey? {
|
||||
guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil }
|
||||
|
||||
return extractPublicKey(cert, policy: SecPolicyCreateBasicX509())
|
||||
@@ -207,7 +318,7 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
|
||||
public func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
|
||||
var possibleTrust: SecTrust?
|
||||
SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
|
||||
|
||||
@@ -224,7 +335,7 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
|
||||
- returns: the certificate chain for the trust
|
||||
*/
|
||||
func certificateChain(_ trust: SecTrust) -> [Data] {
|
||||
public func certificateChain(_ trust: SecTrust) -> [Data] {
|
||||
let certificates = (0..<SecTrustGetCertificateCount(trust)).reduce([Data]()) { (certificates: [Data], index: Int) -> [Data] in
|
||||
var certificates = certificates
|
||||
let cert = SecTrustGetCertificateAtIndex(trust, index)
|
||||
@@ -242,7 +353,7 @@ open class SSLSecurity : SSLTrustValidator {
|
||||
|
||||
- returns: the public keys from the certifcate chain for the trust
|
||||
*/
|
||||
func publicKeyChain(_ trust: SecTrust) -> [SecKey] {
|
||||
public func publicKeyChain(_ trust: SecTrust) -> [SecKey] {
|
||||
let policy = SecPolicyCreateBasicX509()
|
||||
let keys = (0..<SecTrustGetCertificateCount(trust)).reduce([SecKey]()) { (keys: [SecKey], index: Int) -> [SecKey] in
|
||||
var keys = keys
|
||||
@@ -0,0 +1,538 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WSMessageParser.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/25/18.
|
||||
// Copyright (c) 2014-2018 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
|
||||
// Processes and converts raw data into websocket frames
|
||||
// The messages
|
||||
|
||||
import Foundation
|
||||
import CommonCrypto
|
||||
|
||||
struct WSMessage {
|
||||
let code: WebSocket.OpCode
|
||||
let data: Data
|
||||
}
|
||||
|
||||
struct WSFrame {
|
||||
let code: WebSocket.OpCode
|
||||
let bytesLeft: Int
|
||||
let data: Data
|
||||
}
|
||||
|
||||
protocol WSMessageParserDelegate: class {
|
||||
func didReceive(message: WSMessage)
|
||||
func didEncounter(error: WSError)
|
||||
func didParseHTTP(response: String)
|
||||
}
|
||||
|
||||
protocol WSMessageParserClient {
|
||||
func append(data: Data)
|
||||
func createSendFrame(data: Data, code: WebSocket.OpCode) -> Data
|
||||
func reset()
|
||||
}
|
||||
|
||||
class WSMessageParser: WSMessageParserClient {
|
||||
public weak var delegate: WSMessageParserDelegate?
|
||||
public var headerSecurityKey: String {
|
||||
return headerSecKey
|
||||
}
|
||||
|
||||
struct CompressionState {
|
||||
var supportsCompression = false
|
||||
var messageNeedsDecompression = false
|
||||
var serverMaxWindowBits = 15
|
||||
var clientMaxWindowBits = 15
|
||||
var clientNoContextTakeover = false
|
||||
var serverNoContextTakeover = false
|
||||
var decompressor: Decompressor? = nil
|
||||
var compressor: Compressor? = nil
|
||||
}
|
||||
|
||||
let FinMask: UInt8 = 0x80
|
||||
let OpCodeMask: UInt8 = 0x0F
|
||||
let RSVMask: UInt8 = 0x70
|
||||
let RSV1Mask: UInt8 = 0x40
|
||||
let MaskMask: UInt8 = 0x80
|
||||
let PayloadLenMask: UInt8 = 0x7F
|
||||
let httpSwitchProtocolCode = 101
|
||||
let MaxFrameSize: Int = 32
|
||||
|
||||
private var inputQueue = [Data]()
|
||||
private var fragBuffer: Data?
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.messages", attributes: [])
|
||||
private var readStack = [WSFrame]()
|
||||
private let emptyBuffer = UnsafeBufferPointer<UInt8>(start: nil, count: 0)
|
||||
private var didHandshake = false
|
||||
private var compressionState = CompressionState()
|
||||
private var headerSecKey = WSMessageParser.generateWebSocketKey()
|
||||
private let frameHeaderLength = MemoryLayout<UInt8>.size * 2
|
||||
|
||||
// add the data to the queue to be processed
|
||||
func append(data: Data) {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let process = self.inputQueue.count == 0
|
||||
self.inputQueue.append(data)
|
||||
if process {
|
||||
self.dequeue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// creates a websocket frame out of the data you wish to send to the WebSocket server
|
||||
func createSendFrame(data: Data, code: WebSocket.OpCode) -> Data {
|
||||
var offset = frameHeaderLength
|
||||
var firstByte: UInt8 = FinMask | code.rawValue
|
||||
var data = data
|
||||
if [.text, .binary].contains(code), let compressor = compressionState.compressor {
|
||||
do {
|
||||
data = try compressor.compress(data)
|
||||
if compressionState.clientNoContextTakeover {
|
||||
try compressor.reset()
|
||||
}
|
||||
firstByte |= RSV1Mask
|
||||
} catch {
|
||||
//report error? We can just send the uncompressed frame.
|
||||
}
|
||||
}
|
||||
let dataLength = data.count
|
||||
var dataBuffer = Data(capacity: dataLength + MaxFrameSize)
|
||||
|
||||
let frame = dataBuffer.withUnsafeMutableBytes { (buffer: UnsafeMutablePointer<UInt8>) -> Data in
|
||||
buffer[0] = firstByte
|
||||
if dataLength < 126 {
|
||||
buffer[1] = CUnsignedChar(dataLength)
|
||||
} else if dataLength <= Int(UInt16.max) {
|
||||
buffer[1] = 126
|
||||
WSMessageParser.writeUint16(buffer, offset: offset, value: UInt16(dataLength))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
} else {
|
||||
buffer[1] = 127
|
||||
WSMessageParser.writeUint64(buffer, offset: offset, value: UInt64(dataLength))
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
}
|
||||
buffer[1] |= MaskMask
|
||||
let maskKey = UnsafeMutablePointer<UInt8>(buffer + offset)
|
||||
_ = SecRandomCopyBytes(kSecRandomDefault, Int(MemoryLayout<UInt32>.size), maskKey)
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
|
||||
for i in 0..<dataLength {
|
||||
buffer[offset] = data[i] ^ maskKey[i % MemoryLayout<UInt32>.size]
|
||||
offset += 1
|
||||
}
|
||||
return Data(bytes: buffer, count: offset)
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
func reset() {
|
||||
queue.async { [weak self] in
|
||||
self?.didHandshake = false
|
||||
}
|
||||
}
|
||||
|
||||
///MARK: - parsing methods
|
||||
|
||||
/// read from the input queue until it is empty
|
||||
private func dequeue() {
|
||||
while !inputQueue.isEmpty {
|
||||
autoreleasepool {
|
||||
let data = inputQueue.removeFirst()
|
||||
var work = data
|
||||
if let buffer = fragBuffer {
|
||||
var combine = buffer
|
||||
combine.append(data)
|
||||
work = combine
|
||||
fragBuffer = nil
|
||||
}
|
||||
let length = work.count
|
||||
work.withUnsafeBytes { (buffer: UnsafePointer<UInt8>) in
|
||||
if !didHandshake {
|
||||
processTCPHandshake(buffer, bufferLen: length)
|
||||
} else {
|
||||
processRawMessagesInBuffer(buffer, bufferLen: length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process all messages in the buffer if possible.
|
||||
private func processRawMessagesInBuffer(_ pointer: UnsafePointer<UInt8>, bufferLen: Int) {
|
||||
var buffer = UnsafeBufferPointer(start: pointer, count: bufferLen)
|
||||
repeat {
|
||||
buffer = processOneRawMessage(inBuffer: buffer)
|
||||
} while buffer.count >= frameHeaderLength
|
||||
if buffer.count > 0 {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// process the raw data buffer and parse it into the websocket frames it represents
|
||||
private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
|
||||
guard let baseAddress = buffer.baseAddress else {return emptyBuffer}
|
||||
let bytesAvailable = buffer.count
|
||||
|
||||
//need at least two bytes to know what kind of frame it is.
|
||||
if readStack.last != nil && bytesAvailable < frameHeaderLength {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
return emptyBuffer
|
||||
}
|
||||
|
||||
//handle the current frame
|
||||
if let currentFrame = readStack.last, currentFrame.bytesLeft > 0 {
|
||||
var appendLength = currentFrame.bytesLeft
|
||||
var extraLength = bytesAvailable - currentFrame.bytesLeft
|
||||
|
||||
//this frame still needs more content before it is full
|
||||
let isPartialFrame = currentFrame.bytesLeft > bytesAvailable
|
||||
if isPartialFrame {
|
||||
appendLength = bytesAvailable
|
||||
extraLength = 0 //update for the offset
|
||||
}
|
||||
|
||||
//build buffer
|
||||
var combine = currentFrame.data
|
||||
combine.append(baseAddress, count: appendLength)
|
||||
|
||||
_ = readStack.popLast()
|
||||
if isPartialFrame {
|
||||
readStack.append(WSFrame(code: currentFrame.code, bytesLeft: currentFrame.bytesLeft - appendLength, data: combine))
|
||||
} else {
|
||||
delegate?.didReceive(message: WSMessage(code: currentFrame.code, data: combine))
|
||||
}
|
||||
return buffer.fromOffset(bytesAvailable - extraLength)
|
||||
}
|
||||
|
||||
//new frame!
|
||||
let isFin = (FinMask & baseAddress[0])
|
||||
let receivedOpcodeRawValue = (OpCodeMask & baseAddress[0])
|
||||
let receivedOpcode = WebSocket.OpCode(rawValue: receivedOpcodeRawValue)
|
||||
let isMasked = (MaskMask & baseAddress[1])
|
||||
let payloadLen = (PayloadLenMask & baseAddress[1])
|
||||
var offset = frameHeaderLength //skip past the control opcodes of the frame
|
||||
|
||||
//validate the frame is proper frame
|
||||
if compressionState.supportsCompression && receivedOpcode != .continueFrame {
|
||||
compressionState.messageNeedsDecompression = (RSV1Mask & baseAddress[0]) > 0
|
||||
}
|
||||
|
||||
if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong && !compressionState.messageNeedsDecompression {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "masked and rsv data is not currently supported", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping)
|
||||
if !isControlFrame && (receivedOpcode != .binary && receivedOpcode != .continueFrame &&
|
||||
receivedOpcode != .text && receivedOpcode != .pong) {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "unknown opcode: \(receivedOpcodeRawValue)", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
if isControlFrame && isFin == 0 {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "control frames can't be fragmented", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
|
||||
//process the close code
|
||||
var closeCode = CloseCode.normal.rawValue
|
||||
if receivedOpcode == .connectionClose {
|
||||
if payloadLen == 1 {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
} else if payloadLen > 1 {
|
||||
closeCode = WSMessageParser.readUint16(baseAddress, offset: offset)
|
||||
if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1013 && closeCode < 3000) {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
}
|
||||
}
|
||||
if payloadLen < 2 {
|
||||
delegate?.didEncounter(error: WSError(type: .expectedClose, message: "connection closed by server", code: closeCode))
|
||||
return emptyBuffer
|
||||
}
|
||||
} else if isControlFrame && payloadLen > 125 {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "control frame using extend payload", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
|
||||
//handle the "body" of the message
|
||||
var dataLength = UInt64(payloadLen)
|
||||
if dataLength == 127 {
|
||||
dataLength = WSMessageParser.readUint64(baseAddress, offset: offset)
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
} else if dataLength == 126 {
|
||||
dataLength = UInt64(WSMessageParser.readUint16(baseAddress, offset: offset))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
}
|
||||
if bytesAvailable < offset || UInt64(bytesAvailable - offset) < dataLength {
|
||||
fragBuffer = Data(bytes: baseAddress, count: bytesAvailable)
|
||||
return emptyBuffer
|
||||
}
|
||||
var appendLength = dataLength
|
||||
if dataLength > UInt64(bytesAvailable) {
|
||||
appendLength = UInt64(bytesAvailable-offset)
|
||||
}
|
||||
if receivedOpcode == .connectionClose && appendLength > 0 {
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
offset += size
|
||||
appendLength -= UInt64(size)
|
||||
}
|
||||
|
||||
let data: Data
|
||||
if compressionState.messageNeedsDecompression, let decompressor = compressionState.decompressor {
|
||||
do {
|
||||
data = try decompressor.decompress(bytes: baseAddress+offset, count: Int(appendLength), finish: isFin > 0)
|
||||
if isFin > 0 && compressionState.serverNoContextTakeover {
|
||||
try decompressor.reset()
|
||||
}
|
||||
} catch {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "Decompression failed: \(error)", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
} else {
|
||||
data = Data(bytes: baseAddress+offset, count: Int(appendLength))
|
||||
}
|
||||
|
||||
//handle frames by opcodes
|
||||
if receivedOpcode == .connectionClose {
|
||||
var closeReason = "connection closed by server"
|
||||
if let customCloseReason = String(data: data, encoding: .utf8) {
|
||||
closeReason = customCloseReason
|
||||
} else {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
}
|
||||
delegate?.didEncounter(error: WSError(type: .expectedClose, message: closeReason, code: closeCode))
|
||||
return emptyBuffer
|
||||
}
|
||||
if receivedOpcode == .pong || receivedOpcode == .ping {
|
||||
delegate?.didReceive(message: WSMessage(code: receivedOpcode!, data: data))
|
||||
return buffer.fromOffset(offset + Int(appendLength))
|
||||
}
|
||||
|
||||
if let currentFrame = readStack.last {
|
||||
//handle "old" frame
|
||||
if receivedOpcode != .continueFrame {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "second and beyond of fragment message must be a continue frame", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
var combine = currentFrame.data
|
||||
combine.append(data)
|
||||
_ = readStack.popLast()
|
||||
readStack.append(WSFrame(code: currentFrame.code, bytesLeft: currentFrame.bytesLeft - Int(appendLength), data: combine))
|
||||
} else {
|
||||
//handle new frame
|
||||
if receivedOpcode == .continueFrame {
|
||||
delegate?.didEncounter(error: WSError(type: .protocolError, message: "first frame can't be a continue frame", code: CloseCode.protocolError.rawValue))
|
||||
return emptyBuffer
|
||||
}
|
||||
let left = dataLength - appendLength
|
||||
readStack.append(WSFrame(code: receivedOpcode!, bytesLeft: Int(left), data: data))
|
||||
}
|
||||
|
||||
//process response
|
||||
if let currentFrame = readStack.last, currentFrame.bytesLeft <= 0 && isFin > 0 {
|
||||
_ = readStack.popLast()
|
||||
delegate?.didReceive(message: WSMessage(code: currentFrame.code, data: currentFrame.data))
|
||||
}
|
||||
|
||||
let step = Int(offset + numericCast(appendLength))
|
||||
return buffer.fromOffset(step)
|
||||
}
|
||||
|
||||
///MARK: - TCP/HTTP handling
|
||||
|
||||
/// Handle checking the inital connection status
|
||||
private func processTCPHandshake(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) {
|
||||
let code = processHTTP(buffer, bufferLen: bufferLen)
|
||||
switch code {
|
||||
case 0:
|
||||
break
|
||||
case -1:
|
||||
fragBuffer = Data(bytes: buffer, count: bufferLen)
|
||||
break // do nothing, we are going to collect more data
|
||||
default:
|
||||
delegate?.didEncounter(error: WSError(type: .upgradeError, message: "Invalid HTTP upgrade", code: UInt16(code)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the HTTP Packet in the TCP stream, by looking for the CRLF.
|
||||
private func processHTTP(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
|
||||
let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var k = 0
|
||||
var totalSize = 0
|
||||
for i in 0..<bufferLen {
|
||||
if buffer[i] == CRLFBytes[k] {
|
||||
k += 1
|
||||
if k == 4 {
|
||||
totalSize = i + 1
|
||||
break
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
if totalSize > 0 {
|
||||
let code = validateResponse(buffer, bufferLen: totalSize)
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
didHandshake = true
|
||||
let restSize = bufferLen - totalSize
|
||||
if restSize > 0 {
|
||||
processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize)
|
||||
}
|
||||
return 0 //success
|
||||
}
|
||||
return -1 // Was unable to find the full TCP header.
|
||||
}
|
||||
|
||||
/// Validates the HTTP is a 101 as per the RFC spec.
|
||||
private func validateResponse(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
|
||||
guard let str = String(data: Data(bytes: buffer, count: bufferLen), encoding: .utf8) else { return -1 }
|
||||
let splitArr = str.components(separatedBy: "\r\n")
|
||||
var code = -1
|
||||
var i = 0
|
||||
var headers = [String: String]()
|
||||
for str in splitArr {
|
||||
if i == 0 {
|
||||
let responseSplit = str.components(separatedBy: .whitespaces)
|
||||
guard responseSplit.count > 1 else { return -1 }
|
||||
if let c = Int(responseSplit[1]) {
|
||||
code = c
|
||||
}
|
||||
} else {
|
||||
let responseSplit = str.components(separatedBy: ":")
|
||||
guard responseSplit.count > 1 else { break }
|
||||
let key = responseSplit[0].trimmingCharacters(in: .whitespaces)
|
||||
let val = responseSplit[1].trimmingCharacters(in: .whitespaces)
|
||||
headers[key.lowercased()] = val
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
if code != httpSwitchProtocolCode {
|
||||
return code
|
||||
}
|
||||
|
||||
if let extensionHeader = headers[WebSocket.headerWSExtensionName.lowercased()] {
|
||||
processExtensionHeader(extensionHeader)
|
||||
}
|
||||
|
||||
if let acceptKey = headers[WebSocket.headerWSAcceptName.lowercased()] {
|
||||
if acceptKey.count > 0 {
|
||||
if headerSecKey.count > 0 {
|
||||
let sha = "\(headerSecKey)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64()
|
||||
if sha != acceptKey as String {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
delegate?.didParseHTTP(response: str)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
|
||||
/// Parses the extension header, setting up the compression parameters.
|
||||
func processExtensionHeader(_ extensionHeader: String) {
|
||||
let parts = extensionHeader.components(separatedBy: ";")
|
||||
for p in parts {
|
||||
let part = p.trimmingCharacters(in: .whitespaces)
|
||||
if part == "permessage-deflate" {
|
||||
compressionState.supportsCompression = true
|
||||
} else if part.hasPrefix("server_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
compressionState.serverMaxWindowBits = val
|
||||
}
|
||||
} else if part.hasPrefix("client_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
compressionState.clientMaxWindowBits = val
|
||||
}
|
||||
} else if part == "client_no_context_takeover" {
|
||||
compressionState.clientNoContextTakeover = true
|
||||
} else if part == "server_no_context_takeover" {
|
||||
compressionState.serverNoContextTakeover = true
|
||||
}
|
||||
}
|
||||
if compressionState.supportsCompression {
|
||||
compressionState.decompressor = Decompressor(windowBits: compressionState.serverMaxWindowBits)
|
||||
compressionState.compressor = Compressor(windowBits: compressionState.clientMaxWindowBits)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a WebSocket key as needed in RFC.
|
||||
static func generateWebSocketKey() -> String {
|
||||
var key = ""
|
||||
let seed = 16
|
||||
for _ in 0..<seed {
|
||||
let uni = UnicodeScalar(UInt32(97 + arc4random_uniform(25)))
|
||||
key += "\(Character(uni!))"
|
||||
}
|
||||
let data = key.data(using: String.Encoding.utf8)
|
||||
let baseKey = data?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
|
||||
return baseKey!
|
||||
}
|
||||
|
||||
/// Read a 16 bit big endian value from a buffer
|
||||
private static func readUint16(_ buffer: UnsafePointer<UInt8>, offset: Int) -> UInt16 {
|
||||
return (UInt16(buffer[offset + 0]) << 8) | UInt16(buffer[offset + 1])
|
||||
}
|
||||
|
||||
/// Read a 64 bit big endian value from a buffer
|
||||
private static func readUint64(_ buffer: UnsafePointer<UInt8>, offset: Int) -> UInt64 {
|
||||
var value = UInt64(0)
|
||||
for i in 0...7 {
|
||||
value = (value << 8) | UInt64(buffer[offset + i])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/// Write a 16-bit big endian value to a buffer.
|
||||
static func writeUint16(_ buffer: UnsafeMutablePointer<UInt8>, offset: Int, value: UInt16) {
|
||||
buffer[offset + 0] = UInt8(value >> 8)
|
||||
buffer[offset + 1] = UInt8(value & 0xff)
|
||||
}
|
||||
|
||||
/// Write a 64-bit big endian value to a buffer.
|
||||
private static func writeUint64(_ buffer: UnsafeMutablePointer<UInt8>, offset: Int, value: UInt64) {
|
||||
for i in 0...7 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UnsafeBufferPointer {
|
||||
func fromOffset(_ offset: Int) -> UnsafeBufferPointer<Element> {
|
||||
return UnsafeBufferPointer<Element>(start: baseAddress?.advanced(by: offset), count: count - offset)
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
func sha1Base64() -> String {
|
||||
let data = self.data(using: String.Encoding.utf8)!
|
||||
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
|
||||
data.withUnsafeBytes { _ = CC_SHA1($0, CC_LONG(data.count), &digest) }
|
||||
return Data(bytes: digest).base64EncodedString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,667 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
// Copyright (c) 2014-2018 Dalton Cherry.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
import CommonCrypto
|
||||
|
||||
public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification"
|
||||
public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification"
|
||||
public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName"
|
||||
|
||||
//Standard WebSocket close codes
|
||||
public enum CloseCode : UInt16 {
|
||||
case normal = 1000
|
||||
case goingAway = 1001
|
||||
case protocolError = 1002
|
||||
case protocolUnhandledType = 1003
|
||||
// 1004 reserved.
|
||||
case noStatusReceived = 1005
|
||||
//1006 reserved.
|
||||
case encoding = 1007
|
||||
case policyViolated = 1008
|
||||
case messageTooBig = 1009
|
||||
}
|
||||
|
||||
public enum ErrorType: Error {
|
||||
case outputStreamWriteError //output stream error during write
|
||||
case compressionError
|
||||
case invalidSSLError //Invalid SSL certificate
|
||||
case writeTimeoutError //The socket timed out waiting to be ready to write
|
||||
case protocolError //There was an error parsing the WebSocket frames
|
||||
case upgradeError //There was an error during the HTTP upgrade
|
||||
case closeError //There was an error during the close (socket probably has been dereferenced)
|
||||
case expectedClose //This was a proper close code from the websocket
|
||||
}
|
||||
|
||||
public struct WSError: Error {
|
||||
public let type: ErrorType
|
||||
public let message: String
|
||||
public let code: UInt16
|
||||
}
|
||||
|
||||
//WebSocketClient is setup to be dependency injection for testing
|
||||
public protocol WebSocketClient: class {
|
||||
var delegate: WebSocketDelegate? {get set}
|
||||
var pongDelegate: WebSocketPongDelegate? {get set}
|
||||
var isConnected: Bool {get}
|
||||
|
||||
func connect()
|
||||
func disconnect(forceTimeout: TimeInterval?, closeCode: UInt16)
|
||||
func write(string: String, completion: (() -> ())?)
|
||||
func write(data: Data, completion: (() -> ())?)
|
||||
func write(ping: Data, completion: (() -> ())?)
|
||||
func write(pong: Data, completion: (() -> ())?)
|
||||
}
|
||||
|
||||
//implements some of the base behaviors
|
||||
extension WebSocketClient {
|
||||
public func write(string: String) {
|
||||
write(string: string, completion: nil)
|
||||
}
|
||||
|
||||
public func write(data: Data) {
|
||||
write(data: data, completion: nil)
|
||||
}
|
||||
|
||||
public func write(ping: Data) {
|
||||
write(ping: ping, completion: nil)
|
||||
}
|
||||
|
||||
public func write(pong: Data) {
|
||||
write(pong: pong, completion: nil)
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
disconnect(forceTimeout: nil, closeCode: CloseCode.normal.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol WSStreamDelegate: class {
|
||||
func streamIsWaitingForConnectivity()
|
||||
func streamBetterPathUpdate(isBetter: Bool)
|
||||
func streamPathViabilityUpdate(isViable: Bool)
|
||||
func newBytesInStream()
|
||||
func streamDidError(error: Error?)
|
||||
}
|
||||
|
||||
/// This protocol is to allow custom implemention of the underlining stream.
|
||||
/// This way custom socket libraries can be used.
|
||||
public protocol WSStream {
|
||||
var delegate: WSStreamDelegate? {get set}
|
||||
func connect(url: URL, port: Int, timeout: TimeInterval, useSSL: Bool, completion: @escaping ((Error?) -> Void))
|
||||
func write(data: Data, completion: @escaping ((Error?) -> Void))
|
||||
func read() -> Data?
|
||||
func cleanup()
|
||||
func isValidSSLCertificate() -> Bool
|
||||
}
|
||||
|
||||
//WebSocket implementation
|
||||
|
||||
//standard delegate you should use
|
||||
public protocol WebSocketDelegate: class {
|
||||
func websocketDidConnect(socket: WebSocketClient)
|
||||
|
||||
/// Websocket is waiting for connectivity. Socket may connect in the future.
|
||||
/// Available only with NetworkStream.
|
||||
///
|
||||
/// - Parameter socket: Socket
|
||||
func websocketIsWaitingForConnectivity(socket: WebSocketClient)
|
||||
|
||||
/// Informs the delegate about the viability of current socket path. The
|
||||
/// path becomes invalid when you lose connectivity, but may come back up
|
||||
/// again later.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - socket: Socket
|
||||
/// - isPathViable: Viability of the current path
|
||||
func websocket(_ socket: WebSocketClient, isPathViable: Bool)
|
||||
|
||||
/// Called when network changes and there might be a better path available.
|
||||
/// For example, when you are using cellular data and walk into a building you
|
||||
/// get a Wifi connection. This is when you should attempt to migrate to
|
||||
/// new connection by opening up a new socket. Close the old socket only when
|
||||
/// you got the new socket up. This method may be called again during the
|
||||
/// the time you are making the new connection and the better path is no longer
|
||||
/// available. You can then stop migrating and continue using the old socket.
|
||||
///
|
||||
/// Use in combination with connection viability
|
||||
/// ```
|
||||
/// // Handle connection viability
|
||||
/// socket.onPathViableUpdate = { (isViable) in
|
||||
/// if (!isViable) {
|
||||
/// // Handle connection temporarily losing connectivity
|
||||
/// } else {
|
||||
/// // Handle connection return to connectivity
|
||||
/// }
|
||||
/// }
|
||||
/// // Handle better paths
|
||||
/// socket.onBetterPathUpdate = { (betterPathAvailable) in
|
||||
/// if (betterPathAvailable) {
|
||||
/// // Start a new connection if migration is possible
|
||||
/// } else {
|
||||
/// // Stop any attempts to migrate
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// - Parameters:
|
||||
/// - socket: Socket
|
||||
/// - isBetterPathAvailable: Availability of a better path
|
||||
func websocket(_ socket: WebSocketClient, isBetterPathAvailable: Bool)
|
||||
func websocketDidDisconnect(socket: WebSocketClient, error: Error?)
|
||||
func websocketDidReceiveMessage(socket: WebSocketClient, text: String)
|
||||
func websocketDidReceiveData(socket: WebSocketClient, data: Data)
|
||||
}
|
||||
|
||||
//got pongs
|
||||
public protocol WebSocketPongDelegate: class {
|
||||
func websocketDidReceivePong(socket: WebSocketClient, data: Data?)
|
||||
}
|
||||
|
||||
// A Delegate for see the HTTP upgrade request and response.
|
||||
public protocol WebSocketHTTPDelegate: class {
|
||||
func websocketHttpUpgrade(socket: WebSocket, request: String)
|
||||
func websocketHttpUpgrade(socket: WebSocket, response: String)
|
||||
}
|
||||
|
||||
|
||||
open class WebSocket: NSObject, StreamDelegate, WebSocketClient, WSStreamDelegate, WSMessageParserDelegate {
|
||||
|
||||
public enum OpCode : UInt8 {
|
||||
case continueFrame = 0x0
|
||||
case text = 0x1
|
||||
case binary = 0x2
|
||||
// 3-7 are reserved.
|
||||
case connectionClose = 0x8
|
||||
case ping = 0x9
|
||||
case pong = 0xA
|
||||
// B-F reserved.
|
||||
}
|
||||
|
||||
public static let ErrorDomain = "WebSocket"
|
||||
|
||||
// Where the callback is executed. It defaults to the main UI thread queue.
|
||||
public var callbackQueue = DispatchQueue.main
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
static let headerWSUpgradeName = "Upgrade"
|
||||
static let headerWSUpgradeValue = "websocket"
|
||||
static let headerWSHostName = "Host"
|
||||
static let headerWSConnectionName = "Connection"
|
||||
static let headerWSConnectionValue = "Upgrade"
|
||||
static let headerWSProtocolName = "Sec-WebSocket-Protocol"
|
||||
static let headerWSVersionName = "Sec-WebSocket-Version"
|
||||
static let headerWSVersionValue = "13"
|
||||
static let headerWSExtensionName = "Sec-WebSocket-Extensions"
|
||||
static let headerWSKeyName = "Sec-WebSocket-Key"
|
||||
static let headerOriginName = "Origin"
|
||||
static let headerWSAcceptName = "Sec-WebSocket-Accept"
|
||||
let supportedSSLSchemes = ["wss", "https"]
|
||||
|
||||
// MARK: - Delegates
|
||||
|
||||
/// Responds to callback about new messages coming in over the WebSocket
|
||||
/// and also connection/disconnect messages.
|
||||
public weak var delegate: WebSocketDelegate?
|
||||
|
||||
/// The optional http delegate to see the HTTP request body and response
|
||||
public weak var httpDelegate: WebSocketHTTPDelegate?
|
||||
|
||||
/// Receives a callback for each pong message recived.
|
||||
public weak var pongDelegate: WebSocketPongDelegate?
|
||||
|
||||
public var onConnect: (() -> Void)?
|
||||
public var onDisconnect: ((Error?) -> Void)?
|
||||
public var onWaitingForConnectivity: (() -> Void)?
|
||||
public var onBetterPathUpdate: ((Bool) -> Void)?
|
||||
public var onPathViableUpdate: ((Bool) -> Void)?
|
||||
public var onText: ((String) -> Void)?
|
||||
public var onData: ((Data) -> Void)?
|
||||
public var onPong: ((Data?) -> Void)?
|
||||
public var onHttpResponseHeaders: (([String: String]) -> Void)?
|
||||
|
||||
public var isConnected: Bool {
|
||||
mutex.lock()
|
||||
let isConnected = connected
|
||||
mutex.unlock()
|
||||
return isConnected
|
||||
}
|
||||
|
||||
public var request: URLRequest //this is only public to allow headers, timeout, etc to be modified on reconnect
|
||||
public var currentURL: URL { return request.url! }
|
||||
|
||||
public var respondToPingWithPong: Bool = true
|
||||
public var enableCompression = true
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var stream: WSStream
|
||||
private var parser = WSMessageParser()
|
||||
private var connected = false
|
||||
private var isConnecting = false
|
||||
private let mutex = NSLock()
|
||||
private var writeQueue = DispatchQueue(label: "com.vluxe.starscream.wsframe", attributes: [])
|
||||
|
||||
private var certValidated = false
|
||||
private var didDisconnect = false
|
||||
private var readyToWrite = false
|
||||
private var canDispatch: Bool {
|
||||
mutex.lock()
|
||||
let canWork = readyToWrite
|
||||
mutex.unlock()
|
||||
return canWork
|
||||
}
|
||||
|
||||
/**
|
||||
main init method.
|
||||
- Parameter request: The request to start the WebSocket connection with. This includes custom headers, timeout, etc
|
||||
- Parameter protocols: the protocols to send to the websocket server. This is things like "chat" or "superchat".
|
||||
- Parameter stream: The WSStream to use for the underlying connection. This also includes your security options.
|
||||
*/
|
||||
public init(request: URLRequest, protocols: [String]? = nil, stream: WSStream = FoundationStream()) {
|
||||
self.request = request
|
||||
self.stream = stream
|
||||
if request.value(forHTTPHeaderField: WebSocket.headerOriginName) == nil, let url = request.url {
|
||||
var origin = url.absoluteString
|
||||
if let hostUrl = URL (string: "/", relativeTo: url) {
|
||||
origin = hostUrl.absoluteString
|
||||
origin.remove(at: origin.index(before: origin.endIndex))
|
||||
}
|
||||
self.request.setValue(origin, forHTTPHeaderField: WebSocket.headerOriginName)
|
||||
}
|
||||
if let protocols = protocols, !protocols.isEmpty {
|
||||
self.request.setValue(protocols.joined(separator: ","), forHTTPHeaderField: WebSocket.headerWSProtocolName)
|
||||
}
|
||||
super.init()
|
||||
parser.delegate = self
|
||||
}
|
||||
|
||||
/**
|
||||
convenience init to use a URL instead of a URLRequest. Defaults to 5 second timeout.
|
||||
- Parameter url: is where to connect the websocket too.
|
||||
- Parameter protocols: the protocols to send to the websocket server. This is things like "chat" or "superchat".
|
||||
- Parameter stream: The WSStream to use for the underlying connection. This also includes your security options.
|
||||
*/
|
||||
public convenience init(url: URL, protocols: [String]? = nil, stream: WSStream = FoundationStream()) {
|
||||
var request = URLRequest(url: url)
|
||||
request.timeoutInterval = 5
|
||||
self.init(request: request, protocols: protocols, stream: stream)
|
||||
}
|
||||
|
||||
/**
|
||||
Connect to the WebSocket server on a background thread.
|
||||
*/
|
||||
open func connect() {
|
||||
guard !isConnecting else { return }
|
||||
didDisconnect = false
|
||||
isConnecting = true
|
||||
createHTTPRequest()
|
||||
}
|
||||
|
||||
/**
|
||||
Disconnect from the server. I send a Close control frame to the server, then expect the server to respond with a Close control frame and close the socket from its end. I notify my delegate once the socket has been closed.
|
||||
|
||||
If you supply a non-nil `forceTimeout`, I wait at most that long (in seconds) for the server to close the socket. After the timeout expires, I close the socket and notify my delegate.
|
||||
|
||||
If you supply a zero (or negative) `forceTimeout`, I immediately close the socket (without sending a Close control frame) and notify my delegate.
|
||||
|
||||
- Parameter forceTimeout: Maximum time to wait for the server to close the socket.
|
||||
- Parameter closeCode: The code to send on disconnect. The default is the normal close code for cleanly disconnecting a webSocket.
|
||||
*/
|
||||
open func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
guard isConnected else { return }
|
||||
switch forceTimeout {
|
||||
case .some(let seconds) where seconds > 0:
|
||||
let milliseconds = Int(seconds * 1_000)
|
||||
callbackQueue.asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { [weak self] in
|
||||
self?.disconnectStream(nil)
|
||||
}
|
||||
fallthrough
|
||||
case .none:
|
||||
writeError(closeCode)
|
||||
default:
|
||||
disconnectStream(nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a string to the websocket. This sends it as a text frame.
|
||||
|
||||
If you supply a non-nil completion block, I will perform it when the write completes.
|
||||
|
||||
- parameter string: The string to write.
|
||||
- parameter completion: The (optional) completion handler.
|
||||
*/
|
||||
open func write(string: String, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
writeFrame(string.data(using: .utf8)!, code: .text, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Write binary data to the websocket. This sends it as a binary frame.
|
||||
|
||||
If you supply a non-nil completion block, I will perform it when the write completes.
|
||||
|
||||
- parameter data: The data to write.
|
||||
- parameter completion: The (optional) completion handler.
|
||||
*/
|
||||
open func write(data: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
writeFrame(data, code: .binary, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Write a ping to the websocket. This sends it as a control frame.
|
||||
Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s
|
||||
*/
|
||||
open func write(ping: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
writeFrame(ping, code: .ping, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Write a pong to the websocket. This sends it as a control frame.
|
||||
Respond to a Yodel.
|
||||
*/
|
||||
open func write(pong: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
writeFrame(pong, code: .pong, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/// MARK: - private methods
|
||||
|
||||
/// Starts the connection.
|
||||
private func createHTTPRequest() {
|
||||
guard let url = request.url else { return }
|
||||
var port = url.port
|
||||
if port == nil {
|
||||
if supportedSSLSchemes.contains(url.scheme!) {
|
||||
port = 443
|
||||
} else {
|
||||
port = 80
|
||||
}
|
||||
}
|
||||
request.setValue(WebSocket.headerWSUpgradeValue, forHTTPHeaderField: WebSocket.headerWSUpgradeName)
|
||||
request.setValue(WebSocket.headerWSConnectionValue, forHTTPHeaderField: WebSocket.headerWSConnectionName)
|
||||
request.setValue(WebSocket.headerWSVersionValue, forHTTPHeaderField: WebSocket.headerWSVersionName)
|
||||
request.setValue(parser.headerSecurityKey, forHTTPHeaderField: WebSocket.headerWSKeyName)
|
||||
|
||||
if enableCompression {
|
||||
let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
|
||||
request.setValue(val, forHTTPHeaderField: WebSocket.headerWSExtensionName)
|
||||
}
|
||||
let hostValue = request.allHTTPHeaderFields?[WebSocket.headerWSHostName] ?? "\(url.host!):\(port!)"
|
||||
request.setValue(hostValue, forHTTPHeaderField: WebSocket.headerWSHostName)
|
||||
|
||||
var path = url.absoluteString
|
||||
let offset = (url.scheme?.count ?? 2) + 3
|
||||
path = String(path[path.index(path.startIndex, offsetBy: offset)..<path.endIndex])
|
||||
if let range = path.range(of: "/") {
|
||||
path = String(path[range.lowerBound..<path.endIndex])
|
||||
} else {
|
||||
path = "/"
|
||||
if let query = url.query {
|
||||
path += "?" + query
|
||||
}
|
||||
}
|
||||
|
||||
var httpBody = "\(request.httpMethod ?? "GET") \(path) HTTP/1.1\r\n"
|
||||
if let headers = request.allHTTPHeaderFields {
|
||||
for (key, val) in headers {
|
||||
httpBody += "\(key): \(val)\r\n"
|
||||
}
|
||||
}
|
||||
httpBody += "\r\n"
|
||||
|
||||
initStreamsWithData(httpBody.data(using: .utf8)!, Int(port!))
|
||||
httpDelegate?.websocketHttpUpgrade(socket: self, request: httpBody)
|
||||
}
|
||||
|
||||
/// Start the stream connection and write the data to the output stream.
|
||||
private func initStreamsWithData(_ data: Data, _ port: Int) {
|
||||
|
||||
guard let url = request.url else {
|
||||
disconnectStream(nil, runDelegate: true)
|
||||
return
|
||||
|
||||
}
|
||||
// Disconnect and clean up any existing streams before setting up a new one
|
||||
disconnectStream(nil, runDelegate: false)
|
||||
|
||||
let useSSL = supportedSSLSchemes.contains(url.scheme!)
|
||||
|
||||
certValidated = !useSSL
|
||||
let timeout = request.timeoutInterval * 1_000_000
|
||||
stream.delegate = self
|
||||
stream.connect(url: url, port: port, timeout: timeout, useSSL: useSSL, completion: { [weak self] (error) in
|
||||
guard let self = self else {return}
|
||||
if error != nil {
|
||||
self.disconnectStream(error)
|
||||
return
|
||||
}
|
||||
self.writeQueue.async {
|
||||
// Do SSL pinning
|
||||
if !self.certValidated {
|
||||
self.certValidated = self.stream.isValidSSLCertificate()
|
||||
if !self.certValidated {
|
||||
self.disconnectStream(WSError(type: .invalidSSLError, message: "Invalid SSL certificate", code: 0))
|
||||
return
|
||||
}
|
||||
}
|
||||
self.stream.write(data: data, completion: { (error) in
|
||||
if let error = error {
|
||||
self.disconnectStream(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
self.mutex.lock()
|
||||
self.readyToWrite = true
|
||||
self.mutex.unlock()
|
||||
}
|
||||
|
||||
/// MARK: - WSStreamDelegate
|
||||
|
||||
public func streamIsWaitingForConnectivity() {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onWaitingForConnectivity?()
|
||||
s.delegate?.websocketIsWaitingForConnectivity(socket: s)
|
||||
}
|
||||
}
|
||||
|
||||
public func streamBetterPathUpdate(isBetter: Bool) {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onBetterPathUpdate?(isBetter)
|
||||
s.delegate?.websocket(s, isBetterPathAvailable: isBetter)
|
||||
}
|
||||
}
|
||||
|
||||
public func streamPathViabilityUpdate(isViable: Bool) {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onPathViableUpdate?(isViable)
|
||||
s.delegate?.websocket(s, isPathViable: isViable)
|
||||
}
|
||||
}
|
||||
|
||||
public func newBytesInStream() {
|
||||
guard let data = stream.read() else { return }
|
||||
parser.append(data: data)
|
||||
}
|
||||
|
||||
public func streamDidError(error: Error?) {
|
||||
disconnectStream(error)
|
||||
}
|
||||
|
||||
///MARK: - WSMessageParserDelegate
|
||||
|
||||
func didReceive(message: WSMessage) {
|
||||
switch message.code {
|
||||
case .ping:
|
||||
handlePing(message)
|
||||
case .text:
|
||||
handleText(message)
|
||||
case .binary:
|
||||
handleBinary(message)
|
||||
case .pong:
|
||||
handlePong(message)
|
||||
case .connectionClose:
|
||||
disconnectStream(nil) // should never fall into this (handled in streamDidError)
|
||||
case .continueFrame:
|
||||
break //should never fall into this
|
||||
}
|
||||
}
|
||||
|
||||
func didEncounter(error: WSError) {
|
||||
writeError(error.code)
|
||||
}
|
||||
|
||||
func didParseHTTP(response: String) {
|
||||
mutex.lock()
|
||||
connected = true
|
||||
isConnecting = false
|
||||
didDisconnect = false
|
||||
mutex.unlock()
|
||||
guard canDispatch else { return }
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onConnect?()
|
||||
s.delegate?.websocketDidConnect(socket: s)
|
||||
s.httpDelegate?.websocketHttpUpgrade(socket: s, response: response)
|
||||
NotificationCenter.default.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - message handlers
|
||||
|
||||
func handlePing(_ message: WSMessage) {
|
||||
if respondToPingWithPong {
|
||||
writeFrame(message.data, code: .pong)
|
||||
}
|
||||
}
|
||||
|
||||
func handleText(_ message: WSMessage) {
|
||||
guard canDispatch, let str = String(data: message.data, encoding: .utf8) else {
|
||||
writeError(CloseCode.encoding.rawValue)
|
||||
return
|
||||
}
|
||||
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onText?(str)
|
||||
s.delegate?.websocketDidReceiveMessage(socket: s, text: str)
|
||||
}
|
||||
}
|
||||
|
||||
func handleBinary(_ message: WSMessage) {
|
||||
guard canDispatch else { return }
|
||||
|
||||
let data = message.data
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onData?(data)
|
||||
s.delegate?.websocketDidReceiveData(socket: s, data: data as Data)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePong(_ message: WSMessage) {
|
||||
guard canDispatch else { return }
|
||||
|
||||
let pongData: Data? = message.data.count > 0 ? message.data : nil
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onPong?(pongData)
|
||||
s.pongDelegate?.websocketDidReceivePong(socket: s, data: pongData)
|
||||
}
|
||||
}
|
||||
|
||||
//// Disconnect the stream object and notifies the delegate.
|
||||
private func disconnectStream(_ error: Error?, runDelegate: Bool = true) {
|
||||
mutex.lock()
|
||||
stream.cleanup()
|
||||
parser.reset()
|
||||
connected = false
|
||||
mutex.unlock()
|
||||
if runDelegate {
|
||||
doDisconnect(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to preform the disconnect delegate
|
||||
private func doDisconnect(_ error: Error?) {
|
||||
guard !didDisconnect else { return }
|
||||
didDisconnect = true
|
||||
isConnecting = false
|
||||
guard canDispatch else { return }
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.onDisconnect?(error)
|
||||
self.delegate?.websocketDidDisconnect(socket: self, error: error)
|
||||
let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] }
|
||||
NotificationCenter.default.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an error to the socket
|
||||
private func writeError(_ code: UInt16) {
|
||||
let buf = NSMutableData(capacity: MemoryLayout<UInt16>.size)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
WSMessageParser.writeUint16(buffer, offset: 0, value: code)
|
||||
writeFrame(Data(bytes: buffer, count: MemoryLayout<UInt16>.size), code: .connectionClose)
|
||||
}
|
||||
|
||||
/// Used to write things to the stream
|
||||
private func writeFrame(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) {
|
||||
writeQueue.async { [weak self] in
|
||||
guard let s = self, s.connected else { return }
|
||||
let frame = s.parser.createSendFrame(data: data, code: code)
|
||||
s.stream.write(data: frame, completion: {[weak self] (error) in
|
||||
self?.callbackQueue.async {
|
||||
writeCompletion?()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
mutex.lock()
|
||||
readyToWrite = false
|
||||
stream.cleanup()
|
||||
mutex.unlock()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if swift(>=4)
|
||||
#else
|
||||
fileprivate extension String {
|
||||
var count: Int {
|
||||
return self.characters.count
|
||||
}
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
+3
-7
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Starscream"
|
||||
s.version = "3.0.1"
|
||||
s.version = "3.0.6"
|
||||
s.summary = "A conforming WebSocket RFC 6455 client library in Swift."
|
||||
s.homepage = "https://github.com/daltoniam/Starscream"
|
||||
s.license = 'Apache License, Version 2.0'
|
||||
@@ -11,10 +11,6 @@ Pod::Spec.new do |s|
|
||||
s.osx.deployment_target = '10.10'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
s.watchos.deployment_target = '2.0'
|
||||
s.source_files = 'Sources/*.swift'
|
||||
s.libraries = 'z'
|
||||
s.pod_target_xcconfig = {
|
||||
'SWIFT_INCLUDE_PATHS' => '$(PODS_ROOT)/Starscream/zlib'
|
||||
}
|
||||
s.preserve_paths = 'zlib/*'
|
||||
s.source_files = 'Sources/**/*.swift'
|
||||
s.swift_version = '4.2'
|
||||
end
|
||||
|
||||
@@ -7,37 +7,37 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
335FA1F61F5DF71D00F6D2EC /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88EAF7E1ED4DFB5004FE2C3 /* Compression.swift */; };
|
||||
335FA1F71F5DF71D00F6D2EC /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */; };
|
||||
335FA1F81F5DF71D00F6D2EC /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1360011C473BEF00AA3A01 /* WebSocket.swift */; };
|
||||
335FA1F91F5DF71D00F6D2EC /* CompressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88EAF831ED4E7D8004FE2C3 /* CompressionTests.swift */; };
|
||||
335FA1FA1F5DF71D00F6D2EC /* StarscreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 742419BB1DC6BDBA003ACE43 /* StarscreamTests.swift */; };
|
||||
335FA1FC1F5DF71D00F6D2EC /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D88EAF811ED4DFD3004FE2C3 /* libz.tbd */; };
|
||||
33CCF0861F5DDC030099B092 /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = D88EAF7E1ED4DFB5004FE2C3 /* Compression.swift */; };
|
||||
33CCF0871F5DDC030099B092 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1360011C473BEF00AA3A01 /* WebSocket.swift */; };
|
||||
33CCF0881F5DDC030099B092 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */; };
|
||||
33CCF08A1F5DDC030099B092 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D88EAF811ED4DFD3004FE2C3 /* libz.tbd */; };
|
||||
33CCF08C1F5DDC030099B092 /* Starscream.h in Headers */ = {isa = PBXBuildFile; fileRef = 5C1360001C473BEF00AA3A01 /* Starscream.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
33CCF08D1F5DDC030099B092 /* include.h in Headers */ = {isa = PBXBuildFile; fileRef = D85927D71ED76F25003460CB /* include.h */; };
|
||||
633624AF219AD2F80053CB46 /* NetworkStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633624AC219AD2F80053CB46 /* NetworkStream.swift */; };
|
||||
633624B0219AD2F80053CB46 /* FoundationStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633624AD219AD2F80053CB46 /* FoundationStream.swift */; };
|
||||
633624B1219AD2F80053CB46 /* WSMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 633624AE219AD2F80053CB46 /* WSMessage.swift */; };
|
||||
BBB5ABE5215E2217005B48B6 /* Compression.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB5ABE1215E2217005B48B6 /* Compression.swift */; };
|
||||
BBB5ABE6215E2217005B48B6 /* SSLClientCertificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB5ABE2215E2217005B48B6 /* SSLClientCertificate.swift */; };
|
||||
BBB5ABE7215E2217005B48B6 /* SSLSecurity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB5ABE3215E2217005B48B6 /* SSLSecurity.swift */; };
|
||||
BBB5ABE8215E2217005B48B6 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB5ABE4215E2217005B48B6 /* WebSocket.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
335FA2021F5DF71D00F6D2EC /* Starscream Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Starscream Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CCF0921F5DDC030099B092 /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SSLSecurity.swift; path = Sources/SSLSecurity.swift; sourceTree = SOURCE_ROOT; };
|
||||
5C1360001C473BEF00AA3A01 /* Starscream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Starscream.h; path = Sources/Starscream.h; sourceTree = SOURCE_ROOT; };
|
||||
5C1360011C473BEF00AA3A01 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Sources/WebSocket.swift; sourceTree = SOURCE_ROOT; };
|
||||
5C13600C1C473BFE00AA3A01 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = SOURCE_ROOT; };
|
||||
5CAAB5D01F7987D800F3C556 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS4.0.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
633624AC219AD2F80053CB46 /* NetworkStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NetworkStream.swift; path = Starscream/NetworkStream.swift; sourceTree = "<group>"; };
|
||||
633624AD219AD2F80053CB46 /* FoundationStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FoundationStream.swift; path = Starscream/FoundationStream.swift; sourceTree = "<group>"; };
|
||||
633624AE219AD2F80053CB46 /* WSMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WSMessage.swift; path = Starscream/WSMessage.swift; sourceTree = "<group>"; };
|
||||
63895636219AD95900C3C085 /* TestConnection.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = TestConnection.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
6B3E7A0019D48C2F006071F7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
742419BB1DC6BDBA003ACE43 /* StarscreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StarscreamTests.swift; path = StarscreamTests/StarscreamTests.swift; sourceTree = "<group>"; };
|
||||
D85927D61ED761A0003460CB /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
|
||||
D85927D71ED76F25003460CB /* include.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = include.h; sourceTree = "<group>"; };
|
||||
D88EAF7E1ED4DFB5004FE2C3 /* Compression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Compression.swift; path = Sources/Compression.swift; sourceTree = SOURCE_ROOT; };
|
||||
BBB5ABE1215E2217005B48B6 /* Compression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Compression.swift; path = Starscream/Compression.swift; sourceTree = "<group>"; };
|
||||
BBB5ABE2215E2217005B48B6 /* SSLClientCertificate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SSLClientCertificate.swift; path = Starscream/SSLClientCertificate.swift; sourceTree = "<group>"; };
|
||||
BBB5ABE3215E2217005B48B6 /* SSLSecurity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SSLSecurity.swift; path = Starscream/SSLSecurity.swift; sourceTree = "<group>"; };
|
||||
BBB5ABE4215E2217005B48B6 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Starscream/WebSocket.swift; sourceTree = "<group>"; tabWidth = 4; };
|
||||
D88EAF811ED4DFD3004FE2C3 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
D88EAF831ED4E7D8004FE2C3 /* CompressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompressionTests.swift; sourceTree = "<group>"; };
|
||||
D88EAF8D1ED4E92E004FE2C3 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
D88EAF901ED4E949004FE2C3 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS10.2.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -63,7 +63,7 @@
|
||||
6B3E79DC19D48B7F006071F7 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D85927D51ED761A0003460CB /* zlib */,
|
||||
63895636219AD95900C3C085 /* TestConnection.playground */,
|
||||
6B3E79E819D48B7F006071F7 /* Sources */,
|
||||
6B3E79FF19D48C2F006071F7 /* Tests */,
|
||||
6B3E79E719D48B7F006071F7 /* Products */,
|
||||
@@ -83,10 +83,14 @@
|
||||
6B3E79E819D48B7F006071F7 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
633624AD219AD2F80053CB46 /* FoundationStream.swift */,
|
||||
633624AC219AD2F80053CB46 /* NetworkStream.swift */,
|
||||
633624AE219AD2F80053CB46 /* WSMessage.swift */,
|
||||
BBB5ABE1215E2217005B48B6 /* Compression.swift */,
|
||||
BBB5ABE2215E2217005B48B6 /* SSLClientCertificate.swift */,
|
||||
BBB5ABE3215E2217005B48B6 /* SSLSecurity.swift */,
|
||||
BBB5ABE4215E2217005B48B6 /* WebSocket.swift */,
|
||||
5C1360001C473BEF00AA3A01 /* Starscream.h */,
|
||||
5C135FFF1C473BEF00AA3A01 /* SSLSecurity.swift */,
|
||||
5C1360011C473BEF00AA3A01 /* WebSocket.swift */,
|
||||
D88EAF7E1ED4DFB5004FE2C3 /* Compression.swift */,
|
||||
6B3E79E919D48B7F006071F7 /* Supporting Files */,
|
||||
);
|
||||
path = Sources;
|
||||
@@ -110,21 +114,9 @@
|
||||
path = Tests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D85927D51ED761A0003460CB /* zlib */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D85927D61ED761A0003460CB /* module.modulemap */,
|
||||
D85927D71ED76F25003460CB /* include.h */,
|
||||
);
|
||||
path = zlib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D88EAF801ED4DFD3004FE2C3 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CAAB5D01F7987D800F3C556 /* libz.tbd */,
|
||||
D88EAF901ED4E949004FE2C3 /* libz.tbd */,
|
||||
D88EAF8D1ED4E92E004FE2C3 /* libz.tbd */,
|
||||
D88EAF811ED4DFD3004FE2C3 /* libz.tbd */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -138,7 +130,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CCF08C1F5DDC030099B092 /* Starscream.h in Headers */,
|
||||
33CCF08D1F5DDC030099B092 /* include.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -194,6 +185,10 @@
|
||||
335FA1F41F5DF71D00F6D2EC = {
|
||||
LastSwiftMigration = 0900;
|
||||
};
|
||||
33CCF0841F5DDC030099B092 = {
|
||||
LastSwiftMigration = 0940;
|
||||
ProvisioningStyle = Manual;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 6B3E79E019D48B7F006071F7 /* Build configuration list for PBXProject "Starscream" */;
|
||||
@@ -236,9 +231,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
335FA1F61F5DF71D00F6D2EC /* Compression.swift in Sources */,
|
||||
335FA1F71F5DF71D00F6D2EC /* SSLSecurity.swift in Sources */,
|
||||
335FA1F81F5DF71D00F6D2EC /* WebSocket.swift in Sources */,
|
||||
335FA1F91F5DF71D00F6D2EC /* CompressionTests.swift in Sources */,
|
||||
335FA1FA1F5DF71D00F6D2EC /* StarscreamTests.swift in Sources */,
|
||||
);
|
||||
@@ -248,9 +240,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
33CCF0861F5DDC030099B092 /* Compression.swift in Sources */,
|
||||
33CCF0871F5DDC030099B092 /* WebSocket.swift in Sources */,
|
||||
33CCF0881F5DDC030099B092 /* SSLSecurity.swift in Sources */,
|
||||
BBB5ABE5215E2217005B48B6 /* Compression.swift in Sources */,
|
||||
BBB5ABE8215E2217005B48B6 /* WebSocket.swift in Sources */,
|
||||
633624B0219AD2F80053CB46 /* FoundationStream.swift in Sources */,
|
||||
633624B1219AD2F80053CB46 /* WSMessage.swift in Sources */,
|
||||
BBB5ABE7215E2217005B48B6 /* SSLSecurity.swift in Sources */,
|
||||
BBB5ABE6215E2217005B48B6 /* SSLClientCertificate.swift in Sources */,
|
||||
633624AF219AD2F80053CB46 /* NetworkStream.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -278,6 +274,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator macosx";
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
@@ -301,6 +298,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator macosx";
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
@@ -314,6 +312,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -324,18 +323,19 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LDFLAGS = "-all_load";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos watchos watchsimulator";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
VALID_ARCHS = "x86_64 i386 arm64 armv7s armv7";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
VALID_ARCHS = "x86_64 i386 arm64 armv7s armv7 armv7k";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -347,6 +347,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -357,18 +358,19 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
OTHER_LDFLAGS = "";
|
||||
OTHER_LDFLAGS = "-all_load";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE = "";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SDKROOT = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos watchos watchsimulator";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TVOS_DEPLOYMENT_TARGET = 10.0;
|
||||
VALID_ARCHS = "x86_64 i386 arm64 armv7s armv7";
|
||||
TVOS_DEPLOYMENT_TARGET = 9.0;
|
||||
VALID_ARCHS = "x86_64 i386 arm64 armv7s armv7 armv7k";
|
||||
WATCHOS_DEPLOYMENT_TARGET = 2.0;
|
||||
};
|
||||
name = Release;
|
||||
@@ -424,7 +426,6 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
|
||||
SWIFT_INCLUDE_PATHS = $SRCROOT/zlib;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
@@ -477,7 +478,6 @@
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = "";
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
|
||||
SWIFT_INCLUDE_PATHS = $SRCROOT/zlib;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,3,4";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import Starscream
|
||||
import PlaygroundSupport
|
||||
|
||||
PlaygroundPage.current.needsIndefiniteExecution = true
|
||||
|
||||
let websocket = WebSocket(request: URLRequest(url: URL(string: "ws://echo.websocket.org")!), stream: NetworkStream())
|
||||
|
||||
websocket.onConnect = {
|
||||
print("connected")
|
||||
|
||||
websocket.write(string: "Hello")
|
||||
}
|
||||
|
||||
websocket.onDisconnect = { error in
|
||||
print("error:", error)
|
||||
}
|
||||
|
||||
websocket.onText = { text in
|
||||
print(text)
|
||||
}
|
||||
|
||||
websocket.connect()
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='5.0' target-platform='macos' executeOnSourceChanges='false'>
|
||||
<timeline fileName='timeline.xctimeline'/>
|
||||
</playground>
|
||||
@@ -20,6 +20,7 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import XCTest
|
||||
@testable import Starscream
|
||||
|
||||
class CompressionTests: XCTestCase {
|
||||
|
||||
@@ -52,8 +53,9 @@ class CompressionTests: XCTestCase {
|
||||
// 2 Gigs!
|
||||
// var rawData = Data(repeating: 0, count: 0x80000000)
|
||||
var rawData = Data(repeating: 0, count: 0x80000)
|
||||
let rawDataLen = rawData.count
|
||||
rawData.withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
arc4random_buf(ptr, rawData.count)
|
||||
arc4random_buf(ptr, rawDataLen)
|
||||
}
|
||||
|
||||
let compressed = try! compressor.compress(rawData)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// FakeStream.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/26/18.
|
||||
// Copyright © 2018 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class FakeStream: WSStream, TestServerDelegate {
|
||||
var delegate: WSStreamDelegate?
|
||||
let server: TestServer
|
||||
var buffer: Data?
|
||||
|
||||
init(server: TestServer) {
|
||||
self.server = server
|
||||
self.server.delegate = self
|
||||
}
|
||||
|
||||
func connect(url: URL, port: Int, timeout: TimeInterval, useSSL: Bool, completion: @escaping ((Error?) -> Void)) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
|
||||
self.server.start()
|
||||
completion(nil)
|
||||
})
|
||||
}
|
||||
|
||||
func write(data: Data, completion: @escaping ((Error?) -> Void)) {
|
||||
server.receive(data: data)
|
||||
}
|
||||
|
||||
func read() -> Data? {
|
||||
let data = buffer
|
||||
buffer = nil
|
||||
return data
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
buffer = nil
|
||||
}
|
||||
|
||||
func isValidSSLCertificate() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
///MARK: - TestServerDelegate
|
||||
func didSend(data: Data) {
|
||||
if buffer != nil {
|
||||
buffer?.append(data)
|
||||
} else {
|
||||
buffer = data
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,21 +10,55 @@ import XCTest
|
||||
|
||||
class StarscreamTests: XCTestCase {
|
||||
|
||||
var socket: WebSocket!
|
||||
let testServer = TestServer()
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
let url = URL(string: "http://fakedomain.com")! //not a real request
|
||||
let req = URLRequest(url: url)
|
||||
let fakeStream = FakeStream(server: testServer)
|
||||
socket = WebSocket(request: req, protocols: nil, stream: fakeStream)
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func runSocket() {
|
||||
socket.onText = { [weak self] (text: String) in
|
||||
self?.socket.write(string: text)
|
||||
}
|
||||
socket.onData = { [weak self] (data: Data) in
|
||||
self?.socket.write(data: data)
|
||||
}
|
||||
var once = false
|
||||
socket.onDisconnect = {[weak self] (error: Error?) in
|
||||
if !once {
|
||||
once = true
|
||||
let status = self?.testServer.passed ?? false
|
||||
if status {
|
||||
XCTAssert(true, "Pass")
|
||||
} else {
|
||||
XCTAssert(false, "Failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
socket.connect()
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
XCTAssert(true, "Pass")
|
||||
}
|
||||
|
||||
// func testCase1() {
|
||||
// testServer.testCase = .case1
|
||||
// runSocket()
|
||||
// }
|
||||
|
||||
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure() {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// TestServer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/26/18.
|
||||
// Copyright © 2018 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum TestCase {
|
||||
case case1
|
||||
case case2
|
||||
}
|
||||
|
||||
protocol TestServerDelegate: class {
|
||||
func didSend(data: Data)
|
||||
}
|
||||
|
||||
class TestServer {
|
||||
var testCase: TestCase?
|
||||
weak var delegate: TestServerDelegate?
|
||||
var buffer = Data()
|
||||
var passed = false
|
||||
|
||||
func start() {
|
||||
guard let testCase = testCase else { return }
|
||||
switch testCase {
|
||||
case .case1:
|
||||
case1()
|
||||
case .case2:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func receive(data: Data) {
|
||||
buffer.append(data)
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
buffer = Data()
|
||||
}
|
||||
|
||||
//MARK: - the cases!
|
||||
|
||||
func case1() {
|
||||
//TODO: Websocket server framing
|
||||
//let data = "".data(using: .utf8)!
|
||||
//delegate?.didSend(data: frame)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o pipefail && xcodebuild -project Starscream.xcodeproj -scheme Starscream CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO clean build | xcpretty
|
||||
swift build
|
||||
pod repo update
|
||||
pod lib lint --verbose
|
||||
@@ -31,47 +31,19 @@
|
||||
remoteGlobalIDString = 6B3E79E619D48B7F006071F7;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
5C178E4A1B62D0EF00A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 6B3E79F119D48B7F006071F7;
|
||||
remoteInfo = StarscreamTests;
|
||||
};
|
||||
5C178E4C1B62D0EF00A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D9C3E35F19E48FF1009FC285;
|
||||
remoteInfo = StarscreamOSX;
|
||||
};
|
||||
5C178E4E1B62D0EF00A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D9C3E36919E48FF1009FC285;
|
||||
remoteInfo = StarscreamOSXTests;
|
||||
};
|
||||
5C178E501B62D10A00A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6B3E79E519D48B7F006071F7;
|
||||
remoteGlobalIDString = 33CCF0841F5DDC030099B092;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
5C42C3D51D8DF51C00947AA2 /* PBXContainerItemProxy */ = {
|
||||
5CF15740210A738400869246 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277971BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOS";
|
||||
};
|
||||
5C42C3D71D8DF51C00947AA2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277A01BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOSTests";
|
||||
remoteGlobalIDString = 335FA2021F5DF71D00F6D2EC;
|
||||
remoteInfo = "Starscream Tests";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
@@ -169,11 +141,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E491B62D0EF00A97204 /* Starscream.framework */,
|
||||
5C178E4B1B62D0EF00A97204 /* StarscreamTests.xctest */,
|
||||
5C178E4D1B62D0EF00A97204 /* Starscream.framework */,
|
||||
5C178E4F1B62D0EF00A97204 /* StarscreamOSXTests.xctest */,
|
||||
5C42C3D61D8DF51C00947AA2 /* Starscream.framework */,
|
||||
5C42C3D81D8DF51C00947AA2 /* Starscream tvOSTests.xctest */,
|
||||
5CF15741210A738400869246 /* Starscream Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -230,11 +198,11 @@
|
||||
TargetAttributes = {
|
||||
5C178E1B1B62D0B900A97204 = {
|
||||
CreatedOnToolsVersion = 6.4;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
5C178E301B62D0B900A97204 = {
|
||||
CreatedOnToolsVersion = 6.4;
|
||||
LastSwiftMigration = 0800;
|
||||
LastSwiftMigration = 1000;
|
||||
TestTargetID = 5C178E1B1B62D0B900A97204;
|
||||
};
|
||||
};
|
||||
@@ -272,39 +240,11 @@
|
||||
remoteRef = 5C178E481B62D0EF00A97204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C178E4B1B62D0EF00A97204 /* StarscreamTests.xctest */ = {
|
||||
5CF15741210A738400869246 /* Starscream Tests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream iOSTests.xctest";
|
||||
remoteRef = 5C178E4A1B62D0EF00A97204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C178E4D1B62D0EF00A97204 /* Starscream.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Starscream.framework;
|
||||
remoteRef = 5C178E4C1B62D0EF00A97204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C178E4F1B62D0EF00A97204 /* StarscreamOSXTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream OSXTests.xctest";
|
||||
remoteRef = 5C178E4E1B62D0EF00A97204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C42C3D61D8DF51C00947AA2 /* Starscream.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Starscream.framework;
|
||||
remoteRef = 5C42C3D51D8DF51C00947AA2 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C42C3D81D8DF51C00947AA2 /* Starscream tvOSTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream tvOSTests.xctest";
|
||||
remoteRef = 5C42C3D71D8DF51C00947AA2 /* PBXContainerItemProxy */;
|
||||
path = "Starscream Tests.xctest";
|
||||
remoteRef = 5CF15740210A738400869246 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
@@ -469,10 +409,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Autobahn/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = On;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -480,10 +422,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Autobahn/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = On;
|
||||
SWIFT_VERSION = 4.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -502,7 +446,8 @@
|
||||
INFOPLIST_FILE = AutobahnTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = On;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Autobahn.app/Autobahn";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -518,7 +463,8 @@
|
||||
INFOPLIST_FILE = AutobahnTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = On;
|
||||
SWIFT_VERSION = 4.2;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Autobahn.app/Autobahn";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -14,7 +14,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ class ViewController: UIViewController {
|
||||
|
||||
let host = "localhost:9001"
|
||||
var socketArray = [WebSocket]()
|
||||
var caseCount = 300 //starting cases
|
||||
var caseCount = 320 //starting cases
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
getCaseCount()
|
||||
//getTestInfo(1)
|
||||
//runTest(1)
|
||||
}
|
||||
|
||||
func removeSocket(_ s: WebSocket?) {
|
||||
@@ -26,7 +27,7 @@ class ViewController: UIViewController {
|
||||
|
||||
func getCaseCount() {
|
||||
|
||||
let s = WebSocket(url: URL(string: "ws://\(host)/getCaseCount")!, protocols: [])
|
||||
let s = WebSocket(url: URL(string: "ws://\(host)/getCaseCount")!, protocols: [], stream: FoundationStream()) //NetworkStream
|
||||
socketArray.append(s)
|
||||
s.onText = { [weak self] (text: String) in
|
||||
if let c = Int(text) {
|
||||
@@ -86,10 +87,11 @@ class ViewController: UIViewController {
|
||||
if !once {
|
||||
once = true
|
||||
print("case:\(caseNum) finished")
|
||||
//self?.verifyTest(caseNum) disabled since it slows down the tests
|
||||
//self?.verifyTest(caseNum) //disabled since it slows down the tests
|
||||
let nextCase = caseNum+1
|
||||
if nextCase <= (self?.caseCount)! {
|
||||
self?.getTestInfo(nextCase)
|
||||
self?.runTest(nextCase)
|
||||
//self?.getTestInfo(nextCase) //disabled since it slows down the tests
|
||||
} else {
|
||||
self?.finishReports()
|
||||
}
|
||||
@@ -97,6 +99,12 @@ class ViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
s.connect()
|
||||
//timeout
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 480, execute: {
|
||||
if !once {
|
||||
s.disconnect(forceTimeout: 0, closeCode: CloseCode.normal.rawValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func verifyTest(_ caseNum: Int) {
|
||||
@@ -148,7 +156,7 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
func createSocket(_ cmd: String, _ caseNum: Int) -> WebSocket {
|
||||
return WebSocket(url: URL(string: "ws://\(host)\(buildPath(cmd,caseNum))")!, protocols: [])
|
||||
return WebSocket(url: URL(string: "ws://\(host)\(buildPath(cmd,caseNum))")!, protocols: [], stream: FoundationStream()) //NetworkStream
|
||||
}
|
||||
|
||||
func buildPath(_ cmd: String, _ caseNum: Int) -> String {
|
||||
|
||||
BIN
Binary file not shown.
@@ -10,10 +10,13 @@ import UIKit
|
||||
import Starscream
|
||||
|
||||
class ViewController: UIViewController, WebSocketDelegate {
|
||||
var socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat", "superchat"])
|
||||
var socket: WebSocket!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
var request = URLRequest(url: URL(string: "http://localhost:8080")!)
|
||||
request.timeoutInterval = 5
|
||||
socket = WebSocket(request: request)
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
}
|
||||
@@ -25,7 +28,9 @@ class ViewController: UIViewController, WebSocketDelegate {
|
||||
}
|
||||
|
||||
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
|
||||
if let e = error {
|
||||
if let e = error as? WSError {
|
||||
print("websocket is disconnected: \(e.message)")
|
||||
} else if let e = error {
|
||||
print("websocket is disconnected: \(e.localizedDescription)")
|
||||
} else {
|
||||
print("websocket disconnected")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Uncomment the next line to define a global platform for your project
|
||||
# platform :ios, '9.0'
|
||||
|
||||
target 'WebSocketsOrgEcho' do
|
||||
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
|
||||
use_frameworks!
|
||||
|
||||
# Pods for WebSocketsOrgEcho
|
||||
|
||||
pod 'Starscream', :path => '../../'
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- Starscream (3.0.6)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Starscream (from `../../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Starscream:
|
||||
:path: "../../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
|
||||
|
||||
PODFILE CHECKSUM: 96d91933fe13671aaa81af8a8675ff7698068845
|
||||
|
||||
COCOAPODS: 1.6.0.beta.2
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Starscream",
|
||||
"version": "3.0.6",
|
||||
"summary": "A conforming WebSocket RFC 6455 client library in Swift.",
|
||||
"homepage": "https://github.com/daltoniam/Starscream",
|
||||
"license": "Apache License, Version 2.0",
|
||||
"authors": {
|
||||
"Dalton Cherry": "http://daltoniam.com",
|
||||
"Austin Cherry": "http://austincherry.me"
|
||||
},
|
||||
"source": {
|
||||
"git": "https://github.com/daltoniam/Starscream.git",
|
||||
"tag": "3.0.6"
|
||||
},
|
||||
"social_media_url": "http://twitter.com/daltoniam",
|
||||
"platforms": {
|
||||
"ios": "8.0",
|
||||
"osx": "10.10",
|
||||
"tvos": "9.0",
|
||||
"watchos": "2.0"
|
||||
},
|
||||
"source_files": "Sources/**/*.swift",
|
||||
"swift_version": "4.2"
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
PODS:
|
||||
- Starscream (3.0.6)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Starscream (from `../../`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Starscream:
|
||||
:path: "../../"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
|
||||
|
||||
PODFILE CHECKSUM: 96d91933fe13671aaa81af8a8675ff7698068845
|
||||
|
||||
COCOAPODS: 1.6.0.beta.2
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
# Acknowledgements
|
||||
This application makes use of the following third party libraries:
|
||||
|
||||
## Starscream
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2014-2016 Dalton Cherry.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
Generated by CocoaPods - https://cocoapods.org
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreferenceSpecifiers</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>This application makes use of the following third party libraries:</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string> Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2014-2016 Dalton Cherry.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.</string>
|
||||
<key>License</key>
|
||||
<string>Apache License, Version 2.0</string>
|
||||
<key>Title</key>
|
||||
<string>Starscream</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
<key>Title</key>
|
||||
<string></string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>StringsTable</key>
|
||||
<string>Acknowledgements</string>
|
||||
<key>Title</key>
|
||||
<string>Acknowledgements</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Pods_WebSocketsOrgEcho : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Pods_WebSocketsOrgEcho
|
||||
@end
|
||||
Generated
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
function on_error {
|
||||
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
|
||||
}
|
||||
trap 'on_error $LINENO' ERR
|
||||
|
||||
if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
|
||||
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
|
||||
# frameworks to, so exit 0 (signalling the script phase was successful).
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
|
||||
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
|
||||
# Used as a return value for each invocation of `strip_invalid_archs` function.
|
||||
STRIP_BINARY_RETVAL=0
|
||||
|
||||
# This protects against multiple targets copying the same framework dependency at the same time. The solution
|
||||
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
|
||||
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
|
||||
|
||||
# Copies and strips a vendored framework
|
||||
install_framework()
|
||||
{
|
||||
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$1"
|
||||
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
|
||||
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
|
||||
elif [ -r "$1" ]; then
|
||||
local source="$1"
|
||||
fi
|
||||
|
||||
local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
if [ -L "${source}" ]; then
|
||||
echo "Symlinked..."
|
||||
source="$(readlink "${source}")"
|
||||
fi
|
||||
|
||||
# Use filter instead of exclude so missing patterns don't throw errors.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework "$1")"
|
||||
binary="${destination}/${basename}.framework/${basename}"
|
||||
|
||||
if ! [ -r "$binary" ]; then
|
||||
binary="${destination}/${basename}"
|
||||
elif [ -L "${binary}" ]; then
|
||||
echo "Destination binary is symlinked..."
|
||||
dirname="$(dirname "${binary}")"
|
||||
binary="${dirname}/$(readlink "${binary}")"
|
||||
fi
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
# Resign the code if required by the build settings to avoid unstable apps
|
||||
code_sign_if_enabled "${destination}/$(basename "$1")"
|
||||
|
||||
# Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
|
||||
if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
|
||||
local swift_runtime_libs
|
||||
swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u)
|
||||
for lib in $swift_runtime_libs; do
|
||||
echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
|
||||
rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
|
||||
code_sign_if_enabled "${destination}/${lib}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Copies and strips a vendored dSYM
|
||||
install_dsym() {
|
||||
local source="$1"
|
||||
if [ -r "$source" ]; then
|
||||
# Copy the dSYM into a the targets temp dir.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
|
||||
|
||||
local basename
|
||||
basename="$(basename -s .framework.dSYM "$source")"
|
||||
binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
|
||||
|
||||
# Strip invalid architectures so "fat" simulator / device frameworks work on device
|
||||
if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
|
||||
strip_invalid_archs "$binary"
|
||||
fi
|
||||
|
||||
if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
|
||||
# Move the stripped file into its final destination.
|
||||
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
|
||||
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
|
||||
else
|
||||
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
|
||||
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Signs a framework with the provided identity
|
||||
code_sign_if_enabled() {
|
||||
if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
|
||||
# Use the current code_sign_identity
|
||||
echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
|
||||
local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
|
||||
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
code_sign_cmd="$code_sign_cmd &"
|
||||
fi
|
||||
echo "$code_sign_cmd"
|
||||
eval "$code_sign_cmd"
|
||||
fi
|
||||
}
|
||||
|
||||
# Strip invalid architectures
|
||||
strip_invalid_archs() {
|
||||
binary="$1"
|
||||
# Get architectures for current target binary
|
||||
binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
|
||||
# Intersect them with the architectures we are building for
|
||||
intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
|
||||
# If there are no archs supported by this binary then warn the user
|
||||
if [[ -z "$intersected_archs" ]]; then
|
||||
echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
|
||||
STRIP_BINARY_RETVAL=0
|
||||
return
|
||||
fi
|
||||
stripped=""
|
||||
for arch in $binary_archs; do
|
||||
if ! [[ "${ARCHS}" == *"$arch"* ]]; then
|
||||
# Strip non-valid architectures in-place
|
||||
lipo -remove "$arch" -output "$binary" "$binary"
|
||||
stripped="$stripped $arch"
|
||||
fi
|
||||
done
|
||||
if [[ "$stripped" ]]; then
|
||||
echo "Stripped $binary of architectures:$stripped"
|
||||
fi
|
||||
STRIP_BINARY_RETVAL=1
|
||||
}
|
||||
|
||||
|
||||
if [[ "$CONFIGURATION" == "Debug" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/Starscream/Starscream.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/Starscream/Starscream.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
wait
|
||||
fi
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double Pods_WebSocketsOrgEchoVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char Pods_WebSocketsOrgEchoVersionString[];
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Starscream"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Starscream/Starscream.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Starscream"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
framework module Pods_WebSocketsOrgEcho {
|
||||
umbrella header "Pods-WebSocketsOrgEcho-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Starscream"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Starscream/Starscream.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Starscream"
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
PODS_ROOT = ${SRCROOT}/Pods
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.0.6</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Starscream : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Starscream
|
||||
@end
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
FOUNDATION_EXPORT double StarscreamVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char StarscreamVersionString[];
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
framework module Starscream {
|
||||
umbrella header "Starscream-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Starscream
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/../../..
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:WebSocketsOrgEcho.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// WebSocketsOrgEcho
|
||||
//
|
||||
// Created by Kristaps Grinbergs on 08/10/2018.
|
||||
// Copyright © 2018 Starscream. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="WebSocketsOrgEcho" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ysb-Wt-xJQ">
|
||||
<rect key="frame" x="158" y="318.5" width="59" height="30"/>
|
||||
<state key="normal" title="Connect"/>
|
||||
<connections>
|
||||
<action selector="connect:" destination="BYZ-38-t0r" eventType="touchUpInside" id="JK8-oU-5uC"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="ysb-Wt-xJQ" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="2YK-eB-LZR"/>
|
||||
<constraint firstItem="ysb-Wt-xJQ" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="iGG-QS-5FX"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// URL+Extensions.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Kristaps Grinbergs on 08/10/2018.
|
||||
// Copyright © 2018 Kristaps Grinbergs. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension URL {
|
||||
init(staticString string: StaticString) {
|
||||
guard let url = URL(string: "\(string)") else {
|
||||
preconditionFailure("Invalid static URL string: \(string)")
|
||||
}
|
||||
|
||||
self = url
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// ViewController.swift
|
||||
// WebSocketsOrgEcho
|
||||
//
|
||||
// Created by Kristaps Grinbergs on 08/10/2018.
|
||||
// Copyright © 2018 Starscream. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
import Starscream
|
||||
|
||||
class ViewController: UIViewController, WebSocketDelegate {
|
||||
func websocketIsWaitingForConnectivity(socket: WebSocketClient) {
|
||||
|
||||
}
|
||||
|
||||
func websocket(_ socket: WebSocketClient, isPathViable: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func websocket(_ socket: WebSocketClient, isBetterPathAvailable: Bool) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// var socket: WebSocket = WebSocket(url: URL(staticString: "wss://echo.websocket.org"), stream: NetworkStream())
|
||||
var socket: WebSocket = WebSocket(url: URL(staticString: "wss://echo.websocket.org"))
|
||||
|
||||
func websocketDidConnect(socket: WebSocketClient) {
|
||||
print("websocketDidConnect")
|
||||
}
|
||||
|
||||
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
|
||||
print("websocketDidDisconnect", error ?? "")
|
||||
}
|
||||
|
||||
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
|
||||
print("websocketDidReceiveMessage", text)
|
||||
}
|
||||
|
||||
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
|
||||
print("websocketDidReceiveData", data)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
socket.delegate = self
|
||||
}
|
||||
|
||||
@IBAction func connect(_ sender: Any) {
|
||||
socket.connect()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
default_platform(:ios)
|
||||
|
||||
update_fastlane
|
||||
|
||||
platform :ios do
|
||||
desc "Depoy new version"
|
||||
lane :release do
|
||||
ensure_git_branch
|
||||
version = version_get_podspec(path: "Starscream.podspec")
|
||||
changelog = prompt(text: "Changelog: ", multi_line_end_keyword: "END")
|
||||
|
||||
github_token = ENV['GITHUB_TOKEN']
|
||||
if !github_token || github_token.empty?
|
||||
github_token = prompt(text: "Please enter your GitHub token: ")
|
||||
end
|
||||
|
||||
github_release = set_github_release(
|
||||
repository_name: "daltoniam/Starscream",
|
||||
api_token: github_token,
|
||||
name: version,
|
||||
tag_name: version,
|
||||
description: changelog,
|
||||
commitish: "master"
|
||||
)
|
||||
sh("git fetch --tags")
|
||||
pod_push(allow_warnings: true, verbose: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
fastlane documentation
|
||||
================
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
Install _fastlane_ using
|
||||
```
|
||||
[sudo] gem install fastlane -NV
|
||||
```
|
||||
or alternatively using `brew cask install fastlane`
|
||||
|
||||
# Available Actions
|
||||
## iOS
|
||||
### ios release
|
||||
```
|
||||
fastlane ios release
|
||||
```
|
||||
Depoy new version
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
|
||||
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
|
||||
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
bundle install
|
||||
bundle exec fastlane release
|
||||
@@ -1,2 +0,0 @@
|
||||
#include <zlib.h>
|
||||
#include <CommonCrypto/CommonCrypto.h>
|
||||
@@ -1,9 +0,0 @@
|
||||
module SSCZLib [system] {
|
||||
header "include.h"
|
||||
link "z"
|
||||
export *
|
||||
}
|
||||
module SSCommonCrypto [system] {
|
||||
header "include.h"
|
||||
export *
|
||||
}
|
||||
Reference in New Issue
Block a user