Compare commits
314 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b1e5bb60d | |||
| 4c7b8817a1 | |||
| f8ac424b8e | |||
| dcfffe1114 | |||
| 76fa39ec30 | |||
| ea9a6ff27e | |||
| 66a441a917 | |||
| 04acf17f4c | |||
| 826008daab | |||
| cdb61a3624 | |||
| f900d67759 | |||
| cb131f8ea3 | |||
| 3c5a484049 | |||
| 5ab0d5a541 | |||
| 060600f343 | |||
| f354881550 | |||
| c68359159d | |||
| 2c476c7582 | |||
| 6f18c4938a | |||
| 1435d726ae | |||
| 95eab636fb | |||
| f567b33704 | |||
| 62626d875a | |||
| 7e7d8a42ef | |||
| a0ba26b7de | |||
| b7f15e63c3 | |||
| cebca639ee | |||
| e73800e668 | |||
| 4ae4531e7a | |||
| a55720bd9a | |||
| e95db8ff29 | |||
| 9447264238 | |||
| df8d82047f | |||
| 85c8412646 | |||
| 4820831260 | |||
| 024a27711e | |||
| 5987db1605 | |||
| fe5769f2ae | |||
| 263b9419bb | |||
| 5fbeab4c12 | |||
| 07bccd9c16 | |||
| 51b6c789bd | |||
| 0c7e7abfd7 | |||
| d2f22783b6 | |||
| e6236acc00 | |||
| d003b62b54 | |||
| 70d9e6ec5c | |||
| c0e7d40284 | |||
| 06945ada8b | |||
| fedd77ba8e | |||
| 7e5a96341c | |||
| ce4fcdc967 | |||
| da1d9c69a7 | |||
| b6630118fe | |||
| cfc7b7b8dc | |||
| 311b6dd9c7 | |||
| 339ca39461 | |||
| 4e8973e3fc | |||
| 990a4c858e | |||
| 3b4c81547a | |||
| 9e8fcba42e | |||
| d84552f944 | |||
| c3ac0a9df7 | |||
| 97538bc11d | |||
| 4e8dca654d | |||
| 547cb80ef6 | |||
| 12946fc610 | |||
| 850f620441 | |||
| ced0725e4b | |||
| 9d1bac8ede | |||
| eb29f2a1f9 | |||
| 9cf7b4dfcb | |||
| 6b2742542c | |||
| e14338dd1d | |||
| 0cea1194d2 | |||
| a986e38e56 | |||
| 692f3cb14a | |||
| e8c0e894aa | |||
| b7e98e147f | |||
| e6b65c6d90 | |||
| 7ce91f71ca | |||
| 83ae006b5c | |||
| 394afb99a9 | |||
| 281a49edd8 | |||
| 109dd033fc | |||
| 136274da76 | |||
| 6e3b96ace5 | |||
| b8c4183d68 | |||
| 839bf25f59 | |||
| c946f5c68b | |||
| a2ed45c0b2 | |||
| ccb32575e5 | |||
| 9164a09cf5 | |||
| b386d1ea5a | |||
| c0d564fcc1 | |||
| a9625bf474 | |||
| e68b2f7427 | |||
| 1d81b781e9 | |||
| ebcc04315d | |||
| 692d55c76a | |||
| 520e510c2c | |||
| 4993e492d7 | |||
| d944b0df20 | |||
| 522b7e74ee | |||
| a664af674e | |||
| ec0953ee73 | |||
| 21c38d3c1f | |||
| 1cbfd8126c | |||
| 9c03ef715d | |||
| 7860785cf3 | |||
| b1312f8af1 | |||
| 0e6bdc81e1 | |||
| 514eb3ddb9 | |||
| 4c48638295 | |||
| 429bd1a0b7 | |||
| 5ecb5fd499 | |||
| 63b76e1fd9 | |||
| db7685cf93 | |||
| 5abcb7f11b | |||
| fbd80c2367 | |||
| 75236d6cc7 | |||
| 5f467266fc | |||
| 12e668f84b | |||
| 40cda44e40 | |||
| 51ee77cf6c | |||
| c639b64972 | |||
| 7bb64ba35c | |||
| de293c4b26 | |||
| 688bd32df8 | |||
| ee00c1dfdd | |||
| f8238c495d | |||
| 2d059501d1 | |||
| 07e09a8ab3 | |||
| d58f0a4436 | |||
| 36254e8945 | |||
| 9b942131d5 | |||
| 108aa5e4c8 | |||
| c72659bd32 | |||
| ebdc260ea6 | |||
| 361a905d30 | |||
| 0f18ae5959 | |||
| 82a01218cc | |||
| 46d8cacb2e | |||
| 2280423c21 | |||
| 2022e6f553 | |||
| b0729b1181 | |||
| fd8a3fc3fa | |||
| f58389b4ff | |||
| 4b28929ae5 | |||
| 384b8039ef | |||
| c70d18f03d | |||
| 813d92d917 | |||
| c6dfbf1ccb | |||
| e22724f5cf | |||
| dceb95c4ea | |||
| c3b05cde30 | |||
| 79cd8a2a89 | |||
| a403aea07e | |||
| 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 | |||
| b5991eda48 | |||
| 0a6f6d2a79 | |||
| 3fdf33a078 | |||
| 9f5f0f44f9 | |||
| da7949d2da | |||
| dc48916804 | |||
| e0e8271725 | |||
| 411820d836 | |||
| d326d448b0 | |||
| 7dd90900dc | |||
| f506be54ae | |||
| 143ab16c1a | |||
| 4d281416c2 | |||
| e9d10000ec | |||
| 789264eeff | |||
| 644e8f92c0 | |||
| 68b64ede4e | |||
| 31277a418c | |||
| 21678c9426 | |||
| acbf665b3d | |||
| e99deb9af8 | |||
| 6dc9cdffbc | |||
| ef4ed1b2f9 | |||
| 3401fb25f4 | |||
| daa97005e6 | |||
| 008302326b | |||
| d7c41e513f | |||
| 92816513c3 | |||
| 6c9f76e7ea | |||
| 4d6dce3d64 | |||
| b0ed1decce | |||
| 86310e7bbc | |||
| 7edfb2ad85 | |||
| 1efe3ce798 | |||
| 624a5cc6c3 | |||
| 09b9136ef0 | |||
| 5434be279c | |||
| 08edd54a40 | |||
| 8623fe8b9e | |||
| fa85b9c61c | |||
| 6e9a33a854 | |||
| b5e68fceae | |||
| 601ef74ca4 | |||
| 7e517c246b | |||
| bb11123729 | |||
| 7bf478ce94 | |||
| bce64dabfa | |||
| 3104c289e9 | |||
| f35266534a | |||
| 44fdfa3791 | |||
| 35fc161193 | |||
| 45546818fb | |||
| 19793796bb | |||
| 4ba077ee4b | |||
| ca85642c21 | |||
| ccc9c4ea43 | |||
| 02718e7a45 | |||
| 961fd34f3d | |||
| 87dbce07ea | |||
| ee993322c1 | |||
| 85070aab91 | |||
| 07351dcb77 | |||
| dbeb1190b8 | |||
| 13859364e3 | |||
| 41c0c5e08b | |||
| 60b27a4388 | |||
| a68bf7c35b | |||
| 33a4551f3b | |||
| b22fae407e | |||
| e9160df255 | |||
| c63e173021 | |||
| ddcc30d200 | |||
| 6bacb6f972 | |||
| aca2ad5332 | |||
| 3ba1963c65 | |||
| 3cc4a60e05 | |||
| 14313df360 | |||
| 162c1d53b8 | |||
| b404f98244 | |||
| 8d0c0c1afa | |||
| 816fe413a4 | |||
| 5cc0d860ba | |||
| 4d2f8dbfee | |||
| 126c021e61 | |||
| c13584b99c | |||
| 178c3e8a49 | |||
| ccc1b703dd | |||
| 1b64f78542 | |||
| 549500a503 | |||
| 22d57dca07 | |||
| dd5119cfce | |||
| a6b2e4329b | |||
| 931e3ba8ae | |||
| 7476f5196e | |||
| 20285cce97 | |||
| 8e1de1d475 | |||
| acf64adc24 | |||
| b0fa08cde4 | |||
| 9d32864ab4 | |||
| 7ef2bc41e0 | |||
| 24e32a78e3 |
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report about a bug
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Describe the bug
|
||||
> A clear and concise description of what the bug is.
|
||||
|
||||
### Steps to Reproduce
|
||||
> Detailed steps to reproduce the problematic behavior:
|
||||
|
||||
### Expected behavior
|
||||
> A clear and concise description of what you expected to happen.
|
||||
|
||||
### Environment:
|
||||
- OS/Version: [e.g. iOS/13.3]
|
||||
- Starscream Version [e.g. 4.0.4]
|
||||
- Xcode version [e.g. 11.5]
|
||||
|
||||
### Additional context
|
||||
> Add any other context about the problem here.
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: feature_request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### What do you want to happen?
|
||||
> Please replace this with the general overview of the feature that you'd like to have.
|
||||
|
||||
### What happens now?
|
||||
> Please replace this with of what is happening currently.
|
||||
|
||||
### Demo Code
|
||||
> Any demo code that may used to implement/use the desired feature.
|
||||
|
||||
### Describe alternatives you've considered
|
||||
> A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
### Additional context
|
||||
> Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: General Question
|
||||
about: 'Ask any question about the framework. '
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Question
|
||||
> A description of what you want to know.
|
||||
|
||||
### Environment:
|
||||
- OS/Version: [e.g. iOS/13.3]
|
||||
- Starscream Version [e.g. 4.0.4]
|
||||
- Xcode version [e.g. 11.5]
|
||||
@@ -0,0 +1,8 @@
|
||||
### Issue Link 🔗
|
||||
> Please attach the link to an issue if it exists.
|
||||
|
||||
### Goals ⚽
|
||||
> What you hope to address within this PR.
|
||||
|
||||
### Implementation Details 🚧
|
||||
> Additional details about the PR.
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*.*.*"
|
||||
jobs:
|
||||
release:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set Latest Tag
|
||||
id: vars
|
||||
run: echo "tag=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_OUTPUT
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
- run: bundle exec fastlane test
|
||||
- run: bundle exec fastlane release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
|
||||
TAG: ${{ steps.vars.outputs.tag }}
|
||||
+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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3.2.2
|
||||
@@ -1 +0,0 @@
|
||||
3.0
|
||||
+212
-5
@@ -2,11 +2,218 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### [4.0.4](https://github.com/daltoniam/Starscream/tree/4.0.4)
|
||||
|
||||
Bug fixes for 4.0.3.
|
||||
|
||||
[#808](https://github.com/daltoniam/Starscream/pull/808)
|
||||
[#807](https://github.com/daltoniam/Starscream/pull/807)
|
||||
[#799](https://github.com/daltoniam/Starscream/pull/799)
|
||||
[#797](https://github.com/daltoniam/Starscream/pull/797)
|
||||
[#790](https://github.com/daltoniam/Starscream/pull/790)
|
||||
[#788](https://github.com/daltoniam/Starscream/pull/788)
|
||||
[#777](https://github.com/daltoniam/Starscream/pull/777)
|
||||
[#768](https://github.com/daltoniam/Starscream/pull/768)
|
||||
[#766](https://github.com/daltoniam/Starscream/pull/766)
|
||||
[#764](https://github.com/daltoniam/Starscream/pull/764)
|
||||
|
||||
### [4.0.3](https://github.com/daltoniam/Starscream/tree/4.0.3)
|
||||
|
||||
Bug fixes for 4.0.2.
|
||||
|
||||
[#760](https://github.com/daltoniam/Starscream/issues/760)
|
||||
|
||||
### [4.0.2](https://github.com/daltoniam/Starscream/tree/4.0.2)
|
||||
|
||||
Bug fixes for 4.0.1. Fixed native engine is connected/disconnected. Native engine isn't the default since the API lacks features.
|
||||
|
||||
[#697](https://github.com/daltoniam/Starscream/pull/697)
|
||||
|
||||
### [4.0.1](https://github.com/daltoniam/Starscream/tree/4.0.1)
|
||||
|
||||
Bug fixes for 4.0.0. Enabled Native engine now that the API is out of beta and works properly.
|
||||
|
||||
[#749](https://github.com/daltoniam/Starscream/pull/749)
|
||||
[#755](https://github.com/daltoniam/Starscream/pull/755)
|
||||
|
||||
### [4.0.0](https://github.com/daltoniam/Starscream/tree/4.0.0)
|
||||
|
||||
Major API refactor.
|
||||
|
||||
### [3.1.1](https://github.com/daltoniam/Starscream/tree/3.1.1)
|
||||
|
||||
Small version number fix for 3.1.0: [#703](https://github.com/daltoniam/Starscream/issues/703)
|
||||
|
||||
### [3.1.0](https://github.com/daltoniam/Starscream/tree/3.1.0)
|
||||
|
||||
* Swift 5.0 and Xcode 10.2 support
|
||||
|
||||
#### [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.
|
||||
|
||||
[#389](https://github.com/daltoniam/Starscream/issues/389)
|
||||
[#354](https://github.com/daltoniam/Starscream/issues/354)
|
||||
[#386](https://github.com/daltoniam/Starscream/pull/386)
|
||||
[#388](https://github.com/daltoniam/Starscream/pull/388)
|
||||
[#390](https://github.com/daltoniam/Starscream/pull/390)
|
||||
|
||||
#### [3.0.0](https://github.com/daltoniam/Starscream/tree/3.0.0)
|
||||
|
||||
Major refactor and Swift 4 support. Additions include:
|
||||
|
||||
- Watchos support.
|
||||
- Linux support.
|
||||
- New Stream class to allow custom socket implementations if desired.
|
||||
- Protocol added for mocking (dependency injection).
|
||||
- Single framework (no more platform suffixes! e.g. StarscreamOSX, StarscreamTVOS, etc).
|
||||
|
||||
[#384](https://github.com/daltoniam/Starscream/issues/384)
|
||||
[#377](https://github.com/daltoniam/Starscream/pull/377)
|
||||
[#374](https://github.com/daltoniam/Starscream/issues/374)
|
||||
[#346](https://github.com/daltoniam/Starscream/issues/346)
|
||||
[#335](https://github.com/daltoniam/Starscream/issues/335)
|
||||
[#311](https://github.com/daltoniam/Starscream/pull/311)
|
||||
[#269](https://github.com/daltoniam/Starscream/issues/269)
|
||||
|
||||
#### [2.1.1](https://github.com/daltoniam/Starscream/tree/2.1.1)
|
||||
|
||||
Fixes race condition. Updated to avoid SPM dependencies.
|
||||
|
||||
[#370](https://github.com/daltoniam/Starscream/issues/370)
|
||||
[#367](https://github.com/daltoniam/Starscream/issues/367)
|
||||
[#364](https://github.com/daltoniam/Starscream/pull/364)
|
||||
[#357](https://github.com/daltoniam/Starscream/pull/357)
|
||||
[#355](https://github.com/daltoniam/Starscream/pull/355)
|
||||
|
||||
#### [2.1.0](https://github.com/daltoniam/Starscream/tree/2.1.0)
|
||||
|
||||
Adds WebSocket compression. Also adds advance WebSocket delegate for extra control. Bug Fixes.
|
||||
|
||||
[#349](https://github.com/daltoniam/Starscream/pull/349)
|
||||
[#344](https://github.com/daltoniam/Starscream/pull/344)
|
||||
[#339](https://github.com/daltoniam/Starscream/pull/339)
|
||||
[#337](https://github.com/daltoniam/Starscream/pull/337)
|
||||
[#334](https://github.com/daltoniam/Starscream/issues/334)
|
||||
[#333](https://github.com/daltoniam/Starscream/pull/333)
|
||||
[#319](https://github.com/daltoniam/Starscream/issues/319)
|
||||
[#309](https://github.com/daltoniam/Starscream/issues/309)
|
||||
[#329](https://github.com/daltoniam/Starscream/issues/329)
|
||||
|
||||
#### [2.0.4](https://github.com/daltoniam/Starscream/tree/2.0.4)
|
||||
|
||||
SSL Pinning fix by Giuliano Galea as reported by Lukas Futera of [Centralway](https://www.centralway.com/de/).
|
||||
Warning fixes for Swift 3.1
|
||||
|
||||
#### [2.0.3](https://github.com/daltoniam/Starscream/tree/2.0.3)
|
||||
|
||||
[#302](https://github.com/daltoniam/Starscream/issues/302)
|
||||
[#301](https://github.com/daltoniam/Starscream/issues/301)
|
||||
[#300](https://github.com/daltoniam/Starscream/issues/300)
|
||||
[#296](https://github.com/daltoniam/Starscream/issues/296)
|
||||
[#294](https://github.com/daltoniam/Starscream/issues/294)
|
||||
[#292](https://github.com/daltoniam/Starscream/issues/292)
|
||||
[#289](https://github.com/daltoniam/Starscream/issues/289)
|
||||
[#288](https://github.com/daltoniam/Starscream/issues/288)
|
||||
|
||||
#### [2.0.2](https://github.com/daltoniam/Starscream/tree/2.0.2)
|
||||
|
||||
Fix for the Swift Package Manager.
|
||||
|
||||
Fixed:
|
||||
[#277](https://github.com/daltoniam/Starscream/issues/277)
|
||||
|
||||
#### [2.0.1](https://github.com/daltoniam/Starscream/tree/2.0.1)
|
||||
|
||||
Bug fixes.
|
||||
|
||||
Fixed:
|
||||
[#261](https://github.com/daltoniam/Starscream/issues/261)
|
||||
[#276](https://github.com/daltoniam/Starscream/issues/276)
|
||||
[#267](https://github.com/daltoniam/Starscream/issues/267)
|
||||
[#266](https://github.com/daltoniam/Starscream/issues/266)
|
||||
[#259](https://github.com/daltoniam/Starscream/issues/259)
|
||||
|
||||
#### [2.0.0](https://github.com/daltoniam/Starscream/tree/2.0.0)
|
||||
|
||||
Added Swift 3 support.
|
||||
|
||||
Fixed:
|
||||
Fixed:
|
||||
[#229](https://github.com/daltoniam/Starscream/issues/229)
|
||||
[#232](https://github.com/daltoniam/Starscream/issues/232)
|
||||
|
||||
@@ -16,7 +223,7 @@ Swift 2.3 support.
|
||||
|
||||
#### [1.1.3](https://github.com/daltoniam/Starscream/tree/1.1.3)
|
||||
|
||||
Changed:
|
||||
Changed:
|
||||
[#170](https://github.com/daltoniam/Starscream/issues/170)
|
||||
[#171](https://github.com/daltoniam/Starscream/issues/171)
|
||||
[#174](https://github.com/daltoniam/Starscream/issues/174)
|
||||
@@ -25,14 +232,14 @@ Changed:
|
||||
|
||||
#### [1.1.2](https://github.com/daltoniam/Starscream/tree/1.1.2)
|
||||
|
||||
Fixed:
|
||||
Fixed:
|
||||
[#158](https://github.com/daltoniam/Starscream/issues/158)
|
||||
[#161](https://github.com/daltoniam/Starscream/issues/161)
|
||||
[#164](https://github.com/daltoniam/Starscream/issues/164)
|
||||
|
||||
#### [1.1.1](https://github.com/daltoniam/Starscream/tree/1.1.1)
|
||||
|
||||
Fixed:
|
||||
Fixed:
|
||||
[#157](https://github.com/daltoniam/Starscream/issues/157)
|
||||
|
||||
#### [1.1.0](https://github.com/daltoniam/Starscream/tree/1.1.0)
|
||||
@@ -40,7 +247,7 @@ Fixed:
|
||||
Changed:
|
||||
Moved over to Runloop/default GCD queues to shared queue.
|
||||
|
||||
Fixed:
|
||||
Fixed:
|
||||
[#153](https://github.com/daltoniam/Starscream/issues/153)
|
||||
[#151](https://github.com/daltoniam/Starscream/issues/151)
|
||||
[#150](https://github.com/daltoniam/Starscream/issues/150)
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
rexml
|
||||
activesupport (7.0.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
algoliasearch (1.27.5)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.807.0)
|
||||
aws-sdk-core (3.180.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.132.1)
|
||||
aws-sdk-core (~> 3, >= 3.179.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
cocoapods (1.12.1)
|
||||
addressable (~> 2.8)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.12.1)
|
||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
||||
cocoapods-downloader (>= 1.6.0, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.6.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (>= 2.3.0, < 3.0)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.8.0)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (>= 2.3.0, < 3.0)
|
||||
xcodeproj (>= 1.21.0, < 2.0)
|
||||
cocoapods-core (1.12.1)
|
||||
activesupport (>= 5.0, < 8)
|
||||
addressable (~> 2.8)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix (~> 4.0)
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.5)
|
||||
cocoapods-downloader (1.6.3)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.1)
|
||||
cocoapods-trunk (1.6.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.2.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
concurrent-ruby (1.2.2)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
escape (0.0.4)
|
||||
ethon (0.16.0)
|
||||
ffi (>= 1.15.0)
|
||||
excon (0.100.0)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.214.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.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.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
ffi (1.15.5)
|
||||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.48.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.7.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.19.0)
|
||||
molinillo (0.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
nanaimo (0.3.0)
|
||||
nap (1.1.0)
|
||||
naturally (2.2.1)
|
||||
netrc (0.11.0)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.7.0)
|
||||
public_suffix (4.0.7)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.6)
|
||||
rouge (2.0.7)
|
||||
ruby-macho (2.5.1)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.8.1)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
x86_64-darwin-20
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.4.10
|
||||
@@ -2,7 +2,7 @@
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2014-2016 Dalton Cherry.
|
||||
Copyright (c) 2014-2023 Dalton Cherry.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
|
||||
+15
-1
@@ -1,3 +1,5 @@
|
||||
// swift-tools-version:5.2
|
||||
|
||||
//
|
||||
// Package.Swift
|
||||
// Starscream
|
||||
@@ -21,5 +23,17 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Starscream"
|
||||
name: "Starscream",
|
||||
products: [
|
||||
.library(name: "Starscream", targets: ["Starscream"])
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(name: "Starscream",
|
||||
path: "Sources")
|
||||
]
|
||||
)
|
||||
|
||||
#if os(Linux)
|
||||
package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-zlib-support.git", from: "1.0.0"))
|
||||
#endif
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||

|
||||
|
||||
Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6455)) client library in Swift for iOS and OSX.
|
||||
|
||||
It's Objective-C counter part can be found here: [Jetfire](https://github.com/acmacalister/jetfire)
|
||||
Starscream is a conforming WebSocket ([RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455)) library in Swift.
|
||||
|
||||
## Features
|
||||
|
||||
- Conforms to all of the base [Autobahn test suite](http://autobahn.ws/testsuite/).
|
||||
- Conforms to all of the base [Autobahn test suite](https://crossbar.io/autobahn/).
|
||||
- Nonblocking. Everything happens in the background, thanks to GCD.
|
||||
- TLS/WSS support.
|
||||
- Simple concise codebase at just a few hundred LOC.
|
||||
- Compression Extensions support ([RFC 7692](https://tools.ietf.org/html/rfc7692))
|
||||
|
||||
## Swift 2.3
|
||||
|
||||
See release/tag 1.1.4 for Swift 2.3 support.
|
||||
|
||||
## Example
|
||||
### Import the framework
|
||||
|
||||
First thing is to import the framework. See the Installation instructions on how to add the framework to your project.
|
||||
|
||||
@@ -23,94 +17,65 @@ First thing is to import the framework. See the Installation instructions on how
|
||||
import Starscream
|
||||
```
|
||||
|
||||
### Connect to the WebSocket Server
|
||||
|
||||
Once imported, you can open a connection to your WebSocket server. Note that `socket` is probably best as a property, so it doesn't get deallocated right after being setup.
|
||||
|
||||
```swift
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!)
|
||||
var request = URLRequest(url: URL(string: "http://localhost:8080")!)
|
||||
request.timeoutInterval = 5
|
||||
socket = WebSocket(request: request)
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
```
|
||||
|
||||
After you are connected, there are some delegate methods that we need to implement.
|
||||
After you are connected, there is either a delegate or closure you can use for process WebSocket events.
|
||||
|
||||
### websocketDidConnect
|
||||
### Receiving data from a WebSocket
|
||||
|
||||
websocketDidConnect is called as soon as the client connects to the server.
|
||||
`didReceive` receives all the WebSocket events in a single easy to handle enum.
|
||||
|
||||
```swift
|
||||
func websocketDidConnect(socket: WebSocket) {
|
||||
print("websocket is connected")
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket) {
|
||||
switch event {
|
||||
case .connected(let headers):
|
||||
isConnected = true
|
||||
print("websocket is connected: \(headers)")
|
||||
case .disconnected(let reason, let code):
|
||||
isConnected = false
|
||||
print("websocket is disconnected: \(reason) with code: \(code)")
|
||||
case .text(let string):
|
||||
print("Received text: \(string)")
|
||||
case .binary(let data):
|
||||
print("Received data: \(data.count)")
|
||||
case .ping(_):
|
||||
break
|
||||
case .pong(_):
|
||||
break
|
||||
case .viabilityChanged(_):
|
||||
break
|
||||
case .reconnectSuggested(_):
|
||||
break
|
||||
case .cancelled:
|
||||
isConnected = false
|
||||
case .error(let error):
|
||||
isConnected = false
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### websocketDidDisconnect
|
||||
|
||||
websocketDidDisconnect is called as soon as the client is disconnected from the server.
|
||||
The closure of this would be:
|
||||
|
||||
```swift
|
||||
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
|
||||
print("websocket is disconnected: \(error?.localizedDescription)")
|
||||
socket.onEvent = { event in
|
||||
switch event {
|
||||
// handle events just like above...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### websocketDidReceiveMessage
|
||||
|
||||
websocketDidReceiveMessage is called when the client gets a text frame from the connection.
|
||||
|
||||
```swift
|
||||
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
|
||||
print("got some text: \(text)")
|
||||
}
|
||||
```
|
||||
|
||||
### websocketDidReceiveData
|
||||
|
||||
websocketDidReceiveData is called when the client gets a binary frame from the connection.
|
||||
|
||||
```swift
|
||||
func websocketDidReceiveData(socket: WebSocket, data: Data) {
|
||||
print("got some data: \(data.count)")
|
||||
}
|
||||
```
|
||||
|
||||
### Optional: websocketDidReceivePong *(required protocol: WebSocketPongDelegate)*
|
||||
|
||||
websocketDidReceivePong is called when the client gets a pong response from the connection. You need to implement the WebSocketPongDelegate protocol and set an additional delegate, eg: ` socket.pongDelegate = self`
|
||||
|
||||
```swift
|
||||
func websocketDidReceivePong(socket: WebSocket, data: Data?) {
|
||||
print("Got pong! Maybe some data: \(data?.count)")
|
||||
}
|
||||
```
|
||||
|
||||
Or you can use closures.
|
||||
|
||||
```swift
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!)
|
||||
//websocketDidConnect
|
||||
socket.onConnect = {
|
||||
print("websocket is connected")
|
||||
}
|
||||
//websocketDidDisconnect
|
||||
socket.onDisconnect = { (error: NSError?) in
|
||||
print("websocket is disconnected: \(error?.localizedDescription)")
|
||||
}
|
||||
//websocketDidReceiveMessage
|
||||
socket.onText = { (text: String) in
|
||||
print("got some text: \(text)")
|
||||
}
|
||||
//websocketDidReceiveData
|
||||
socket.onData = { (data: Data) in
|
||||
print("got some data: \(data.count)")
|
||||
}
|
||||
//you could do onPong as well.
|
||||
socket.connect()
|
||||
```
|
||||
|
||||
One more: you can listen to socket connection and disconnection via notifications. Starscream posts `WebsocketDidConnectNotification` and `WebsocketDidDisconnectNotification`. You can find an `NSError` that caused the disconection by accessing `WebsocketDisconnectionErrorKeyName` on notification `userInfo`.
|
||||
|
||||
|
||||
## The delegate methods give you a simple way to handle data from the server, but how do you send data?
|
||||
### Writing to a WebSocket
|
||||
|
||||
### write a binary frame
|
||||
|
||||
@@ -136,6 +101,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.
|
||||
@@ -144,62 +128,52 @@ The disconnect method does what you would expect and closes the socket.
|
||||
socket.disconnect()
|
||||
```
|
||||
|
||||
### isConnected
|
||||
|
||||
Returns if the socket is connected or not.
|
||||
The disconnect method can also send a custom close code if desired.
|
||||
|
||||
```swift
|
||||
if socket.isConnected {
|
||||
// do cool stuff.
|
||||
}
|
||||
socket.disconnect(closeCode: CloseCode.normal.rawValue)
|
||||
```
|
||||
|
||||
### Custom Headers
|
||||
### Custom Headers, Protocols and Timeout
|
||||
|
||||
You can also override the default websocket headers with your own custom ones like so:
|
||||
You can override the default websocket headers, add your own custom ones and set a timeout:
|
||||
|
||||
```swift
|
||||
socket.headers["Sec-WebSocket-Protocol"] = "someother protocols"
|
||||
socket.headers["Sec-WebSocket-Version"] = "14"
|
||||
socket.headers["My-Awesome-Header"] = "Everything is Awesome!"
|
||||
```
|
||||
|
||||
### Protocols
|
||||
|
||||
If you need to specify a protocol, simple add it to the init:
|
||||
|
||||
```swift
|
||||
//chat and superchat are the example protocols here
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
```
|
||||
|
||||
### Self Signed SSL and VOIP
|
||||
|
||||
There are a couple of other properties that modify the stream:
|
||||
|
||||
```swift
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
|
||||
//set this if you are planning on using the socket in a VOIP background setting (using the background VOIP service).
|
||||
socket.voipEnabled = true
|
||||
|
||||
//set this you want to ignore SSL cert validation, so a self signed SSL certificate can be used.
|
||||
socket.disableSSLCertValidation = true
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
request.timeoutInterval = 5 // Sets the timeout for the connection
|
||||
request.setValue("someother protocols", forHTTPHeaderField: "Sec-WebSocket-Protocol")
|
||||
request.setValue("14", forHTTPHeaderField: "Sec-WebSocket-Version")
|
||||
request.setValue("chat,superchat", forHTTPHeaderField: "Sec-WebSocket-Protocol")
|
||||
request.setValue("Everything is Awesome!", forHTTPHeaderField: "My-Awesome-Header")
|
||||
let socket = WebSocket(request: request)
|
||||
```
|
||||
|
||||
### SSL Pinning
|
||||
|
||||
SSL Pinning is also supported in Starscream.
|
||||
SSL Pinning is also supported in Starscream.
|
||||
|
||||
|
||||
Allow Self-signed certificates:
|
||||
|
||||
```swift
|
||||
socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"])
|
||||
let data = ... //load your certificate from disk
|
||||
socket.security = SSLSecurity(certs: [SSLCert(data: data)], usePublicKeys: true)
|
||||
//socket.security = SSLSecurity() //uses the .cer files in your app's bundle
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
let pinner = FoundationSecurity(allowSelfSigned: true) // don't validate SSL certificates
|
||||
let socket = WebSocket(request: request, certPinner: pinner)
|
||||
```
|
||||
You load either a `Data` blob of your certificate or you can use a `SecKeyRef` if you have a public key you want to use. The `usePublicKeys` bool is whether to use the certificates for validation or the public keys. The public keys will be extracted from the certificates automatically if `usePublicKeys` is choosen.
|
||||
|
||||
TODO: Update docs on how to load certificates and public keys into an app bundle, use the builtin pinner and TrustKit.
|
||||
|
||||
### Compression Extensions
|
||||
|
||||
Compression Extensions ([RFC 7692](https://tools.ietf.org/html/rfc7692)) is supported in Starscream. Compression is enabled by default, however compression will only be used if it is supported by the server as well. You may enable compression by adding a `compressionHandler`:
|
||||
|
||||
```swift
|
||||
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
|
||||
let compression = WSCompression()
|
||||
let socket = WebSocket(request: request, compressionHandler: compression)
|
||||
```
|
||||
|
||||
Compression should be disabled if your application is transmitting already-compressed, random, or other uncompressable data.
|
||||
|
||||
### Custom Queue
|
||||
|
||||
@@ -217,7 +191,7 @@ Check out the SimpleTest project in the examples directory to see how to setup a
|
||||
|
||||
## Requirements
|
||||
|
||||
Starscream works with iOS 7/OSX 10.9 or above. It is recommended to use iOS 8/10.10 or above for CocoaPods/framework support. To use Starscream with a project targeting iOS 7, you must include all Swift files directly in your project.
|
||||
Starscream works with iOS 8/10.10 or above for CocoaPods/framework support. To use Starscream with a project targeting iOS 7, you must include all Swift files directly in your project.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -231,7 +205,7 @@ To use Starscream in your project add the following 'Podfile' to your project
|
||||
platform :ios, '9.0'
|
||||
use_frameworks!
|
||||
|
||||
pod 'Starscream', '~> 2.0.0'
|
||||
pod 'Starscream', '~> 4.0.4'
|
||||
|
||||
Then run:
|
||||
|
||||
@@ -253,9 +227,32 @@ $ brew install carthage
|
||||
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
|
||||
|
||||
```
|
||||
github "daltoniam/Starscream" >= 2.0.0
|
||||
github "daltoniam/Starscream" >= 4.0.4
|
||||
```
|
||||
|
||||
### Accio
|
||||
|
||||
Check out the [Accio](https://github.com/JamitLabs/Accio) docs on how to add a install.
|
||||
|
||||
Add the following to your Package.swift:
|
||||
|
||||
```swift
|
||||
.package(url: "https://github.com/daltoniam/Starscream.git", .upToNextMajor(from: "4.0.4")),
|
||||
```
|
||||
|
||||
Next, add `Starscream` to your App targets dependencies like so:
|
||||
|
||||
```swift
|
||||
.target(
|
||||
name: "App",
|
||||
dependencies: [
|
||||
"Starscream",
|
||||
]
|
||||
),
|
||||
```
|
||||
|
||||
Then run `accio update`.
|
||||
|
||||
### Rogue
|
||||
|
||||
First see the [installation docs](https://github.com/acmacalister/Rogue) for how to install Rogue.
|
||||
@@ -263,11 +260,23 @@ First see the [installation docs](https://github.com/acmacalister/Rogue) for how
|
||||
To install Starscream run the command below in the directory you created the rogue file.
|
||||
|
||||
```
|
||||
rogue add https://github.com/daltoniam/starscream
|
||||
rogue add https://github.com/daltoniam/Starscream
|
||||
```
|
||||
|
||||
Next open the `libs` folder and add the `Starscream.xcodeproj` to your Xcode project. Once that is complete, in your "Build Phases" add the `Starscream.framework` to your "Link Binary with Libraries" phase. Make sure to add the `libs` folder to your `.gitignore` file.
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.
|
||||
|
||||
Once you have your Swift package set up, adding Starscream as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/daltoniam/Starscream.git", majorVersion: 4)
|
||||
]
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
Simply grab the framework (either via git submodule or another package manager).
|
||||
@@ -280,9 +289,7 @@ If you are running this in an OSX app or on a physical iOS device you will need
|
||||
|
||||
## TODOs
|
||||
|
||||
- [ ] WatchOS?
|
||||
- [ ] Linux Support?
|
||||
- [ ] Add Unit Tests - Local Swift websocket server
|
||||
- [ ] Proxy support
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?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>2.0.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,256 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SSLSecurity.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 5/16/15.
|
||||
// Copyright (c) 2014-2016 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 Security
|
||||
|
||||
public class SSLCert {
|
||||
var certData: Data?
|
||||
var key: SecKey?
|
||||
|
||||
/**
|
||||
Designated init for certificates
|
||||
|
||||
- parameter data: is the binary data of the certificate
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(data: Data) {
|
||||
self.certData = data
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init for public keys
|
||||
|
||||
- parameter key: is the public key to be used
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(key: SecKey) {
|
||||
self.key = key
|
||||
}
|
||||
}
|
||||
|
||||
public class SSLSecurity {
|
||||
public var validatedDN = true //should the domain name be validated?
|
||||
|
||||
var isReady = false //is the key processing done?
|
||||
var certificates: [Data]? //the certificates
|
||||
var pubKeys: [SecKey]? //the public keys
|
||||
var usePublicKeys = false //use public keys or certificate validation?
|
||||
|
||||
/**
|
||||
Use certs from main app bundle
|
||||
|
||||
- parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public convenience init(usePublicKeys: Bool = false) {
|
||||
let paths = Bundle.main.paths(forResourcesOfType: "cer", inDirectory: ".")
|
||||
|
||||
let certs = paths.reduce([SSLCert]()) { (certs: [SSLCert], path: String) -> [SSLCert] in
|
||||
var certs = certs
|
||||
if let data = NSData(contentsOfFile: path) {
|
||||
certs.append(SSLCert(data: data as Data))
|
||||
}
|
||||
return certs
|
||||
}
|
||||
|
||||
self.init(certs: certs, usePublicKeys: usePublicKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
Designated init
|
||||
|
||||
- parameter keys: is the certificates or public keys to use
|
||||
- parameter usePublicKeys: is to specific if the publicKeys or certificates should be used for SSL pinning validation
|
||||
|
||||
- returns: a representation security object to be used with
|
||||
*/
|
||||
public init(certs: [SSLCert], usePublicKeys: Bool) {
|
||||
self.usePublicKeys = usePublicKeys
|
||||
|
||||
if self.usePublicKeys {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
let pubKeys = certs.reduce([SecKey]()) { (pubKeys: [SecKey], cert: SSLCert) -> [SecKey] in
|
||||
var pubKeys = pubKeys
|
||||
if let data = cert.certData, cert.key == nil {
|
||||
cert.key = self.extractPublicKey(data)
|
||||
}
|
||||
if let key = cert.key {
|
||||
pubKeys.append(key)
|
||||
}
|
||||
return pubKeys
|
||||
}
|
||||
|
||||
self.pubKeys = pubKeys
|
||||
self.isReady = true
|
||||
}
|
||||
} else {
|
||||
let certificates = certs.reduce([Data]()) { (certificates: [Data], cert: SSLCert) -> [Data] in
|
||||
var certificates = certificates
|
||||
if let data = cert.certData {
|
||||
certificates.append(data)
|
||||
}
|
||||
return certificates
|
||||
}
|
||||
self.certificates = certificates
|
||||
self.isReady = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Valid the trust and domain name.
|
||||
|
||||
- parameter trust: is the serverTrust to validate
|
||||
- parameter domain: is the CN domain to validate
|
||||
|
||||
- returns: if the key was successfully validated
|
||||
*/
|
||||
public func isValid(_ trust: SecTrust, domain: String?) -> Bool {
|
||||
|
||||
var tries = 0
|
||||
while !self.isReady {
|
||||
usleep(1000)
|
||||
tries += 1
|
||||
if tries > 5 {
|
||||
return false //doesn't appear it is going to ever be ready...
|
||||
}
|
||||
}
|
||||
var policy: SecPolicy
|
||||
if self.validatedDN {
|
||||
policy = SecPolicyCreateSSL(true, domain as NSString?)
|
||||
} else {
|
||||
policy = SecPolicyCreateBasicX509()
|
||||
}
|
||||
SecTrustSetPolicies(trust,policy)
|
||||
if self.usePublicKeys {
|
||||
if let keys = self.pubKeys {
|
||||
let serverPubKeys = publicKeyChain(trust)
|
||||
for serverKey in serverPubKeys as [AnyObject] {
|
||||
for key in keys as [AnyObject] {
|
||||
if serverKey.isEqual(key) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let certs = self.certificates {
|
||||
let serverCerts = certificateChain(trust)
|
||||
var collect = [SecCertificate]()
|
||||
for cert in certs {
|
||||
collect.append(SecCertificateCreateWithData(nil,cert as CFData)!)
|
||||
}
|
||||
SecTrustSetAnchorCertificates(trust,collect as NSArray)
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust,&result)
|
||||
if result == .unspecified || result == .proceed {
|
||||
var trustedCount = 0
|
||||
for serverCert in serverCerts {
|
||||
for cert in certs {
|
||||
if cert == serverCert {
|
||||
trustedCount += 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if trustedCount == serverCerts.count {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate data
|
||||
|
||||
- parameter data: is the certificate to pull the public key from
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ data: Data) -> SecKey? {
|
||||
guard let cert = SecCertificateCreateWithData(nil, data as CFData) else { return nil }
|
||||
|
||||
return extractPublicKey(cert, policy: SecPolicyCreateBasicX509())
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key from a certificate
|
||||
|
||||
- parameter data: is the certificate to pull the public key from
|
||||
|
||||
- returns: a public key
|
||||
*/
|
||||
func extractPublicKey(_ cert: SecCertificate, policy: SecPolicy) -> SecKey? {
|
||||
var possibleTrust: SecTrust?
|
||||
SecTrustCreateWithCertificates(cert, policy, &possibleTrust)
|
||||
|
||||
guard let trust = possibleTrust else { return nil }
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust, &result)
|
||||
return SecTrustCopyPublicKey(trust)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the certificate chain for the trust
|
||||
|
||||
- parameter trust: is the trust to lookup the certificate chain for
|
||||
|
||||
- returns: the certificate chain for the trust
|
||||
*/
|
||||
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)
|
||||
certificates.append(SecCertificateCopyData(cert!) as Data)
|
||||
return certificates
|
||||
}
|
||||
|
||||
return certificates
|
||||
}
|
||||
|
||||
/**
|
||||
Get the public key chain for the trust
|
||||
|
||||
- parameter trust: is the trust to lookup the certificate chain and extract the public keys
|
||||
|
||||
- returns: the public keys from the certifcate chain for the trust
|
||||
*/
|
||||
func publicKeyChain(_ trust: SecTrust) -> [SecKey] {
|
||||
let policy = SecPolicyCreateBasicX509()
|
||||
let keys = (0..<SecTrustGetCertificateCount(trust)).reduce([SecKey]()) { (keys: [SecKey], index: Int) -> [SecKey] in
|
||||
var keys = keys
|
||||
let cert = SecTrustGetCertificateAtIndex(trust, index)
|
||||
if let key = extractPublicKey(cert!, policy: policy) {
|
||||
keys.append(key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
//
|
||||
// Starscream.h
|
||||
// Starscream
|
||||
//
|
||||
// Created by Austin Cherry on 9/25/14.
|
||||
// Copyright (c) 2014 Vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Starscream.
|
||||
FOUNDATION_EXPORT double StarscreamVersionNumber;
|
||||
|
||||
//! Project version string for Starscream.
|
||||
FOUNDATION_EXPORT const unsigned char StarscreamVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <Starscream/PublicHeader.h>
|
||||
|
||||
|
||||
@@ -1,938 +0,0 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
// Copyright (c) 2014-2016 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 Security
|
||||
|
||||
public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification"
|
||||
public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification"
|
||||
public let WebsocketDisconnectionErrorKeyName = "WebsocketDisconnectionErrorKeyName"
|
||||
|
||||
public protocol WebSocketDelegate: class {
|
||||
func websocketDidConnect(socket: WebSocket)
|
||||
func websocketDidDisconnect(socket: WebSocket, error: NSError?)
|
||||
func websocketDidReceiveMessage(socket: WebSocket, text: String)
|
||||
func websocketDidReceiveData(socket: WebSocket, data: Data)
|
||||
}
|
||||
|
||||
public protocol WebSocketPongDelegate: class {
|
||||
func websocketDidReceivePong(socket: WebSocket, data: Data?)
|
||||
}
|
||||
|
||||
public class WebSocket : NSObject, StreamDelegate {
|
||||
|
||||
enum OpCode : UInt8 {
|
||||
case continueFrame = 0x0
|
||||
case textFrame = 0x1
|
||||
case binaryFrame = 0x2
|
||||
// 3-7 are reserved.
|
||||
case connectionClose = 0x8
|
||||
case ping = 0x9
|
||||
case pong = 0xA
|
||||
// B-F reserved.
|
||||
}
|
||||
|
||||
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 static let ErrorDomain = "WebSocket"
|
||||
|
||||
enum InternalErrorCode: UInt16 {
|
||||
// 0-999 WebSocket status codes not used
|
||||
case outputStreamWriteError = 1
|
||||
}
|
||||
|
||||
// Where the callback is executed. It defaults to the main UI thread queue.
|
||||
public var callbackQueue = DispatchQueue.main
|
||||
|
||||
var optionalProtocols: [String]?
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
let headerWSUpgradeName = "Upgrade"
|
||||
let headerWSUpgradeValue = "websocket"
|
||||
let headerWSHostName = "Host"
|
||||
let headerWSConnectionName = "Connection"
|
||||
let headerWSConnectionValue = "Upgrade"
|
||||
let headerWSProtocolName = "Sec-WebSocket-Protocol"
|
||||
let headerWSVersionName = "Sec-WebSocket-Version"
|
||||
let headerWSVersionValue = "13"
|
||||
let headerWSKeyName = "Sec-WebSocket-Key"
|
||||
let headerOriginName = "Origin"
|
||||
let headerWSAcceptName = "Sec-WebSocket-Accept"
|
||||
let BUFFER_MAX = 4096
|
||||
let FinMask: UInt8 = 0x80
|
||||
let OpCodeMask: UInt8 = 0x0F
|
||||
let RSVMask: UInt8 = 0x70
|
||||
let MaskMask: UInt8 = 0x80
|
||||
let PayloadLenMask: UInt8 = 0x7F
|
||||
let MaxFrameSize: Int = 32
|
||||
let httpSwitchProtocolCode = 101
|
||||
let supportedSSLSchemes = ["wss", "https"]
|
||||
|
||||
class WSResponse {
|
||||
var isFin = false
|
||||
var code: OpCode = .continueFrame
|
||||
var bytesLeft = 0
|
||||
var frameCount = 0
|
||||
var buffer: NSMutableData?
|
||||
}
|
||||
|
||||
// MARK: - Delegates
|
||||
|
||||
/// Responds to callback about new messages coming in over the WebSocket
|
||||
/// and also connection/disconnect messages.
|
||||
public weak var delegate: WebSocketDelegate?
|
||||
|
||||
/// Receives a callback for each pong message recived.
|
||||
public weak var pongDelegate: WebSocketPongDelegate?
|
||||
|
||||
|
||||
// MARK: - Block based API.
|
||||
|
||||
public var onConnect: ((Void) -> Void)?
|
||||
public var onDisconnect: ((NSError?) -> Void)?
|
||||
public var onText: ((String) -> Void)?
|
||||
public var onData: ((Data) -> Void)?
|
||||
public var onPong: ((Data?) -> Void)?
|
||||
|
||||
public var headers = [String: String]()
|
||||
public var voipEnabled = false
|
||||
public var disableSSLCertValidation = false
|
||||
public var security: SSLSecurity?
|
||||
public var enabledSSLCipherSuites: [SSLCipherSuite]?
|
||||
public var origin: String?
|
||||
public var timeout = 5
|
||||
public var isConnected: Bool {
|
||||
return connected
|
||||
}
|
||||
|
||||
public var currentURL: URL { return url }
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private var url: URL
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
private var connected = false
|
||||
private var isConnecting = false
|
||||
private var writeQueue = OperationQueue()
|
||||
private var readStack = [WSResponse]()
|
||||
private var inputQueue = [Data]()
|
||||
private var fragBuffer: Data?
|
||||
private var certValidated = false
|
||||
private var didDisconnect = false
|
||||
private var readyToWrite = false
|
||||
private let mutex = NSLock()
|
||||
private let notificationCenter = NotificationCenter.default
|
||||
private var canDispatch: Bool {
|
||||
mutex.lock()
|
||||
let canWork = readyToWrite
|
||||
mutex.unlock()
|
||||
return canWork
|
||||
}
|
||||
/// The shared processing queue used for all WebSocket.
|
||||
private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
|
||||
|
||||
/// Used for setting protocols.
|
||||
public init(url: URL, protocols: [String]? = nil) {
|
||||
self.url = url
|
||||
self.origin = url.absoluteString
|
||||
writeQueue.maxConcurrentOperationCount = 1
|
||||
optionalProtocols = protocols
|
||||
}
|
||||
|
||||
// Used for specifically setting the QOS for the write queue.
|
||||
public convenience init(url: URL, writeQueueQOS: QualityOfService, protocols: [String]? = nil) {
|
||||
self.init(url: url, protocols: protocols)
|
||||
writeQueue.qualityOfService = writeQueueQOS
|
||||
}
|
||||
|
||||
/**
|
||||
Connect to the WebSocket server on a background thread.
|
||||
*/
|
||||
public func connect() {
|
||||
guard !isConnecting else { return }
|
||||
didDisconnect = false
|
||||
isConnecting = true
|
||||
createHTTPRequest()
|
||||
isConnecting = false
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
public func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
switch forceTimeout {
|
||||
case .some(let seconds) where seconds > 0:
|
||||
callbackQueue.asyncAfter(deadline: DispatchTime.now() + Double(Int64(seconds * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { [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 str: The string to write.
|
||||
- parameter completion: The (optional) completion handler.
|
||||
*/
|
||||
public func write(string: String, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, 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.
|
||||
*/
|
||||
public func write(data: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(data, code: .binaryFrame, 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
|
||||
*/
|
||||
public func write(ping: Data, completion: (() -> ())? = nil) {
|
||||
guard isConnected else { return }
|
||||
dequeueWrite(ping, code: .ping, writeCompletion: completion)
|
||||
}
|
||||
|
||||
/**
|
||||
Private method that starts the connection.
|
||||
*/
|
||||
private func createHTTPRequest() {
|
||||
|
||||
let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString,
|
||||
url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
|
||||
var port = url.port
|
||||
if port == nil {
|
||||
if supportedSSLSchemes.contains(url.scheme!) {
|
||||
port = 443
|
||||
} else {
|
||||
port = 80
|
||||
}
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSUpgradeName, val: headerWSUpgradeValue)
|
||||
addHeader(urlRequest, key: headerWSConnectionName, val: headerWSConnectionValue)
|
||||
if let protocols = optionalProtocols {
|
||||
addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ","))
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue)
|
||||
addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey())
|
||||
if let origin = origin {
|
||||
addHeader(urlRequest, key: headerOriginName, val: origin)
|
||||
}
|
||||
addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)")
|
||||
for (key, value) in headers {
|
||||
addHeader(urlRequest, key: key, val: value)
|
||||
}
|
||||
if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) {
|
||||
let serializedRequest = cfHTTPMessage.takeRetainedValue()
|
||||
initStreamsWithData(serializedRequest as Data, Int(port!))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Add a header to the CFHTTPMessage by using the NSString bridges to CFString
|
||||
*/
|
||||
private func addHeader(_ urlRequest: CFHTTPMessage, key: String, val: String) {
|
||||
CFHTTPMessageSetHeaderFieldValue(urlRequest, key as CFString, val as CFString)
|
||||
}
|
||||
|
||||
/**
|
||||
Generate a WebSocket key as needed in RFC.
|
||||
*/
|
||||
private 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!
|
||||
}
|
||||
|
||||
/**
|
||||
Start the stream connection and write the data to the output stream.
|
||||
*/
|
||||
private func initStreamsWithData(_ data: Data, _ port: Int) {
|
||||
//higher level API we will cut over to at some point
|
||||
//NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream)
|
||||
|
||||
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()
|
||||
guard let inStream = inputStream, let outStream = outputStream else { return }
|
||||
inStream.delegate = self
|
||||
outStream.delegate = self
|
||||
if supportedSSLSchemes.contains(url.scheme!) {
|
||||
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
|
||||
} else {
|
||||
certValidated = true //not a https session, so no need to check SSL pinning
|
||||
}
|
||||
if voipEnabled {
|
||||
inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType)
|
||||
outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType)
|
||||
}
|
||||
if disableSSLCertValidation {
|
||||
let settings: [NSObject: NSObject] = [kCFStreamSSLValidatesCertificateChain: NSNumber(value: false), kCFStreamSSLPeerName: kCFNull]
|
||||
inStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
outStream.setProperty(settings, forKey: kCFStreamPropertySSLSettings as Stream.PropertyKey)
|
||||
}
|
||||
if let cipherSuites = self.enabledSSLCipherSuites {
|
||||
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 {
|
||||
let error = self.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn))
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
if resOut != errSecSuccess {
|
||||
let error = self.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut))
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue)
|
||||
CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue)
|
||||
inStream.open()
|
||||
outStream.open()
|
||||
|
||||
self.mutex.lock()
|
||||
self.readyToWrite = true
|
||||
self.mutex.unlock()
|
||||
|
||||
let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
var out = timeout * 1000000 // wait 5 seconds before giving up
|
||||
writeQueue.addOperation { [weak self] in
|
||||
while !outStream.hasSpaceAvailable {
|
||||
usleep(100) // wait until the socket is ready
|
||||
out -= 100
|
||||
if out < 0 {
|
||||
self?.cleanupStream()
|
||||
self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2))
|
||||
return
|
||||
} else if outStream.streamError != nil {
|
||||
return // disconnectStream will be called.
|
||||
}
|
||||
}
|
||||
outStream.write(bytes, maxLength: data.count)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Delegate for the stream methods. Processes incoming bytes
|
||||
*/
|
||||
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
|
||||
let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as AnyObject
|
||||
let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
|
||||
if sec.isValid(trust as! SecTrust, domain: domain) {
|
||||
certValidated = true
|
||||
} else {
|
||||
let error = errorWithDetail("Invalid SSL certificate", code: 1)
|
||||
disconnectStream(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
if eventCode == .hasBytesAvailable {
|
||||
if aStream == inputStream {
|
||||
processInputStream()
|
||||
}
|
||||
} else if eventCode == .errorOccurred {
|
||||
disconnectStream(aStream.streamError as NSError?)
|
||||
} else if eventCode == .endEncountered {
|
||||
disconnectStream(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Disconnect the stream object and notifies the delegate.
|
||||
*/
|
||||
private func disconnectStream(_ error: NSError?) {
|
||||
if error == nil {
|
||||
writeQueue.waitUntilAllOperationsAreFinished()
|
||||
} else {
|
||||
writeQueue.cancelAllOperations()
|
||||
}
|
||||
cleanupStream()
|
||||
doDisconnect(error)
|
||||
}
|
||||
|
||||
/**
|
||||
cleanup the streams.
|
||||
*/
|
||||
private func cleanupStream() {
|
||||
outputStream?.delegate = nil
|
||||
inputStream?.delegate = nil
|
||||
if let stream = inputStream {
|
||||
CFReadStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
CFWriteStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
outputStream = nil
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
/**
|
||||
Handles the incoming bytes and sending them to the proper processing method.
|
||||
*/
|
||||
private func processInputStream() {
|
||||
let buf = NSMutableData(capacity: BUFFER_MAX)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = inputStream!.read(buffer, maxLength: BUFFER_MAX)
|
||||
|
||||
guard length > 0 else { return }
|
||||
var process = false
|
||||
if inputQueue.count == 0 {
|
||||
process = true
|
||||
}
|
||||
inputQueue.append(Data(bytes: buffer, count: length))
|
||||
if process {
|
||||
dequeueInput()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Dequeue the incoming input so it is processed in order.
|
||||
*/
|
||||
private func dequeueInput() {
|
||||
while !inputQueue.isEmpty {
|
||||
let data = inputQueue[0]
|
||||
var work = data
|
||||
if let fragBuffer = fragBuffer {
|
||||
var combine = NSData(data: fragBuffer) as Data
|
||||
combine.append(data)
|
||||
work = combine
|
||||
self.fragBuffer = nil
|
||||
}
|
||||
let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = work.count
|
||||
if !connected {
|
||||
processTCPHandshake(buffer, bufferLen: length)
|
||||
} else {
|
||||
processRawMessagesInBuffer(buffer, bufferLen: length)
|
||||
}
|
||||
inputQueue = inputQueue.filter{ $0 != data }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Handle checking the inital connection status
|
||||
*/
|
||||
private func processTCPHandshake(_ buffer: UnsafePointer<UInt8>, bufferLen: Int) {
|
||||
let code = processHTTP(buffer, bufferLen: bufferLen)
|
||||
switch code {
|
||||
case 0:
|
||||
connected = true
|
||||
guard canDispatch else {return}
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onConnect?()
|
||||
s.delegate?.websocketDidConnect(socket: s)
|
||||
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
|
||||
}
|
||||
case -1:
|
||||
fragBuffer = Data(bytes: buffer, count: bufferLen)
|
||||
break // do nothing, we are going to collect more data
|
||||
default:
|
||||
doDisconnect(errorWithDetail("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 == 3 {
|
||||
totalSize = i + 1
|
||||
break
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
if totalSize > 0 {
|
||||
let code = validateResponse(buffer, bufferLen: totalSize)
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
totalSize += 1 //skip the last \n
|
||||
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 {
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue()
|
||||
CFHTTPMessageAppendBytes(response, buffer, bufferLen)
|
||||
let code = CFHTTPMessageGetResponseStatusCode(response)
|
||||
if code != httpSwitchProtocolCode {
|
||||
return code
|
||||
}
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let headers = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString {
|
||||
if acceptKey.length > 0 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
private 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Process one message at the start of `buffer`. Return another buffer (sharing storage) that contains the leftover contents of `buffer` that I didn't process.
|
||||
*/
|
||||
private func processOneRawMessage(inBuffer buffer: UnsafeBufferPointer<UInt8>) -> UnsafeBufferPointer<UInt8> {
|
||||
let response = readStack.last
|
||||
guard let baseAddress = buffer.baseAddress else {return emptyBuffer}
|
||||
let bufferLen = buffer.count
|
||||
if response != nil && bufferLen < 2 {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
return emptyBuffer
|
||||
}
|
||||
if let response = response, response.bytesLeft > 0 {
|
||||
var len = response.bytesLeft
|
||||
var extra = bufferLen - response.bytesLeft
|
||||
if response.bytesLeft > bufferLen {
|
||||
len = bufferLen
|
||||
extra = 0
|
||||
}
|
||||
response.bytesLeft -= len
|
||||
response.buffer?.append(Data(bytes: baseAddress, count: len))
|
||||
_ = processResponse(response)
|
||||
return buffer.fromOffset(bufferLen - extra)
|
||||
} else {
|
||||
let isFin = (FinMask & baseAddress[0])
|
||||
let receivedOpcode = OpCode(rawValue: (OpCodeMask & baseAddress[0]))
|
||||
let isMasked = (MaskMask & baseAddress[1])
|
||||
let payloadLen = (PayloadLenMask & baseAddress[1])
|
||||
var offset = 2
|
||||
if (isMasked > 0 || (RSVMask & baseAddress[0]) > 0) && receivedOpcode != .pong {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("masked and rsv data is not currently supported", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
let isControlFrame = (receivedOpcode == .connectionClose || receivedOpcode == .ping)
|
||||
if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame &&
|
||||
receivedOpcode != .textFrame && receivedOpcode != .pong) {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
if isControlFrame && isFin == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("control frames can't be fragmented", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
if receivedOpcode == .connectionClose {
|
||||
var code = CloseCode.normal.rawValue
|
||||
if payloadLen == 1 {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
} else if payloadLen > 1 {
|
||||
code = WebSocket.readUint16(baseAddress, offset: offset)
|
||||
if code < 1000 || (code > 1003 && code < 1007) || (code > 1011 && code < 3000) {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
offset += 2
|
||||
}
|
||||
var closeReason = "connection closed by server"
|
||||
if payloadLen > 2 {
|
||||
let len = Int(payloadLen - 2)
|
||||
if len > 0 {
|
||||
let bytes = baseAddress + offset
|
||||
if let customCloseReason = String(data: Data(bytes: bytes, count: len), encoding: .utf8) {
|
||||
closeReason = customCloseReason
|
||||
} else {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
doDisconnect(errorWithDetail(closeReason, code: code))
|
||||
writeError(code)
|
||||
return emptyBuffer
|
||||
}
|
||||
if isControlFrame && payloadLen > 125 {
|
||||
writeError(CloseCode.protocolError.rawValue)
|
||||
return emptyBuffer
|
||||
}
|
||||
var dataLength = UInt64(payloadLen)
|
||||
if dataLength == 127 {
|
||||
dataLength = WebSocket.readUint64(baseAddress, offset: offset)
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
} else if dataLength == 126 {
|
||||
dataLength = UInt64(WebSocket.readUint16(baseAddress, offset: offset))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
}
|
||||
if bufferLen < offset || UInt64(bufferLen - offset) < dataLength {
|
||||
fragBuffer = Data(bytes: baseAddress, count: bufferLen)
|
||||
return emptyBuffer
|
||||
}
|
||||
var len = dataLength
|
||||
if dataLength > UInt64(bufferLen) {
|
||||
len = UInt64(bufferLen-offset)
|
||||
}
|
||||
let data: Data
|
||||
if len < 0 {
|
||||
len = 0
|
||||
data = Data()
|
||||
} else {
|
||||
data = Data(bytes: baseAddress+offset, count: Int(len))
|
||||
}
|
||||
if receivedOpcode == .pong {
|
||||
if canDispatch {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
let pongData: Data? = data.count > 0 ? data : nil
|
||||
s.onPong?(pongData)
|
||||
s.pongDelegate?.websocketDidReceivePong(socket: s, data: pongData)
|
||||
}
|
||||
}
|
||||
return buffer.fromOffset(offset + Int(len))
|
||||
}
|
||||
var response = readStack.last
|
||||
if isControlFrame {
|
||||
response = nil // Don't append pings.
|
||||
}
|
||||
if isFin == 0 && receivedOpcode == .continueFrame && response == nil {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("continue frame before a binary or text frame", code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
var isNew = false
|
||||
if response == nil {
|
||||
if receivedOpcode == .continueFrame {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("first frame can't be a continue frame",
|
||||
code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
isNew = true
|
||||
response = WSResponse()
|
||||
response!.code = receivedOpcode!
|
||||
response!.bytesLeft = Int(dataLength)
|
||||
response!.buffer = NSMutableData(data: data)
|
||||
} else {
|
||||
if receivedOpcode == .continueFrame {
|
||||
response!.bytesLeft = Int(dataLength)
|
||||
} else {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
doDisconnect(errorWithDetail("second and beyond of fragment message must be a continue frame",
|
||||
code: errCode))
|
||||
writeError(errCode)
|
||||
return emptyBuffer
|
||||
}
|
||||
response!.buffer!.append(data)
|
||||
}
|
||||
if let response = response {
|
||||
response.bytesLeft -= Int(len)
|
||||
response.frameCount += 1
|
||||
response.isFin = isFin > 0 ? true : false
|
||||
if isNew {
|
||||
readStack.append(response)
|
||||
}
|
||||
_ = processResponse(response)
|
||||
}
|
||||
|
||||
let step = Int(offset + numericCast(len))
|
||||
return buffer.fromOffset(step)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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 >= 2
|
||||
if buffer.count > 0 {
|
||||
fragBuffer = Data(buffer: buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Process the finished response of a buffer.
|
||||
*/
|
||||
private func processResponse(_ response: WSResponse) -> Bool {
|
||||
if response.isFin && response.bytesLeft <= 0 {
|
||||
if response.code == .ping {
|
||||
let data = response.buffer! // local copy so it is perverse for writing
|
||||
dequeueWrite(data as Data, code: .pong)
|
||||
} else if response.code == .textFrame {
|
||||
let str: NSString? = NSString(data: response.buffer! as Data, encoding: String.Encoding.utf8.rawValue)
|
||||
if str == nil {
|
||||
writeError(CloseCode.encoding.rawValue)
|
||||
return false
|
||||
}
|
||||
if canDispatch {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onText?(str! as String)
|
||||
s.delegate?.websocketDidReceiveMessage(socket: s, text: str! as String)
|
||||
}
|
||||
}
|
||||
} else if response.code == .binaryFrame {
|
||||
if canDispatch {
|
||||
let data = response.buffer! // local copy so it is perverse for writing
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onData?(data as Data)
|
||||
s.delegate?.websocketDidReceiveData(socket: s, data: data as Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
readStack.removeLast()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Create an error
|
||||
*/
|
||||
private func errorWithDetail(_ detail: String, code: UInt16) -> NSError {
|
||||
var details = [String: String]()
|
||||
details[NSLocalizedDescriptionKey] = detail
|
||||
return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details)
|
||||
}
|
||||
|
||||
/**
|
||||
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)
|
||||
WebSocket.writeUint16(buffer, offset: 0, value: code)
|
||||
dequeueWrite(Data(bytes: buffer, count: MemoryLayout<UInt16>.size), code: .connectionClose)
|
||||
}
|
||||
|
||||
/**
|
||||
Used to write things to the stream
|
||||
*/
|
||||
private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) {
|
||||
writeQueue.addOperation { [weak self] in
|
||||
//stream isn't ready, let's wait
|
||||
guard let s = self else { return }
|
||||
var offset = 2
|
||||
let dataLength = data.count
|
||||
let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize)
|
||||
let buffer = UnsafeMutableRawPointer(frame!.mutableBytes).assumingMemoryBound(to: UInt8.self)
|
||||
buffer[0] = s.FinMask | code.rawValue
|
||||
if dataLength < 126 {
|
||||
buffer[1] = CUnsignedChar(dataLength)
|
||||
} else if dataLength <= Int(UInt16.max) {
|
||||
buffer[1] = 126
|
||||
WebSocket.writeUint16(buffer, offset: offset, value: UInt16(dataLength))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
} else {
|
||||
buffer[1] = 127
|
||||
WebSocket.writeUint64(buffer, offset: offset, value: UInt64(dataLength))
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
}
|
||||
buffer[1] |= s.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
|
||||
}
|
||||
var total = 0
|
||||
while true {
|
||||
guard let outStream = s.outputStream else { break }
|
||||
let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self)
|
||||
let len = outStream.write(writeBuffer, maxLength: offset-total)
|
||||
if len < 0 {
|
||||
var error: Error?
|
||||
if let streamError = outStream.streamError {
|
||||
error = streamError
|
||||
} else {
|
||||
let errCode = InternalErrorCode.outputStreamWriteError.rawValue
|
||||
error = s.errorWithDetail("output stream error during write", code: errCode)
|
||||
}
|
||||
s.doDisconnect(error as NSError?)
|
||||
break
|
||||
} else {
|
||||
total += len
|
||||
}
|
||||
if total >= offset {
|
||||
if let queue = self?.callbackQueue, let callback = writeCompletion {
|
||||
queue.async {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Used to preform the disconnect delegate
|
||||
*/
|
||||
private func doDisconnect(_ error: NSError?) {
|
||||
guard !didDisconnect else { return }
|
||||
didDisconnect = true
|
||||
connected = false
|
||||
guard canDispatch else {return}
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.onDisconnect?(error)
|
||||
s.delegate?.websocketDidDisconnect(socket: s, error: error)
|
||||
let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] }
|
||||
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
mutex.lock()
|
||||
readyToWrite = false
|
||||
mutex.unlock()
|
||||
cleanupStream()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension Data {
|
||||
|
||||
init(buffer: UnsafeBufferPointer<UInt8>) {
|
||||
self.init(bytes: buffer.baseAddress!, count: buffer.count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension UnsafeBufferPointer {
|
||||
|
||||
func fromOffset(_ offset: Int) -> UnsafeBufferPointer<Element> {
|
||||
return UnsafeBufferPointer<Element>(start: baseAddress?.advanced(by: offset), count: count - offset)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private let emptyBuffer = UnsafeBufferPointer<UInt8>(start: nil, count: 0)
|
||||
@@ -0,0 +1,29 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Compression.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 2/4/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public protocol CompressionHandler {
|
||||
func load(headers: [String: String])
|
||||
func decompress(data: Data, isFinal: Bool) -> Data?
|
||||
func compress(data: Data) -> Data?
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WSCompression.swift
|
||||
//
|
||||
// Created by Joseph Ross on 7/16/14.
|
||||
// Copyright © 2017 Joseph Ross & Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Compression implementation is implemented in conformance with RFC 7692 Compression Extensions
|
||||
// for WebSocket: https://tools.ietf.org/html/rfc7692
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import Foundation
|
||||
import zlib
|
||||
|
||||
public class WSCompression: CompressionHandler {
|
||||
let headerWSExtensionName = "Sec-WebSocket-Extensions"
|
||||
var decompressor: Decompressor?
|
||||
var compressor: Compressor?
|
||||
var decompressorTakeOver = false
|
||||
var compressorTakeOver = false
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func load(headers: [String: String]) {
|
||||
guard let extensionHeader = headers[headerWSExtensionName] else { return }
|
||||
decompressorTakeOver = false
|
||||
compressorTakeOver = false
|
||||
|
||||
let parts = extensionHeader.components(separatedBy: ";")
|
||||
for p in parts {
|
||||
let part = p.trimmingCharacters(in: .whitespaces)
|
||||
if part.hasPrefix("server_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
decompressor = Decompressor(windowBits: val)
|
||||
}
|
||||
} else if part.hasPrefix("client_max_window_bits=") {
|
||||
let valString = part.components(separatedBy: "=")[1]
|
||||
if let val = Int(valString.trimmingCharacters(in: .whitespaces)) {
|
||||
compressor = Compressor(windowBits: val)
|
||||
}
|
||||
} else if part == "client_no_context_takeover" {
|
||||
compressorTakeOver = true
|
||||
} else if part == "server_no_context_takeover" {
|
||||
decompressorTakeOver = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
guard let decompressor = decompressor else { return nil }
|
||||
do {
|
||||
let decompressedData = try decompressor.decompress(data, finish: isFinal)
|
||||
if decompressorTakeOver {
|
||||
try decompressor.reset()
|
||||
}
|
||||
return decompressedData
|
||||
} catch {
|
||||
//do nothing with the error for now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func compress(data: Data) -> Data? {
|
||||
guard let compressor = compressor else { return nil }
|
||||
do {
|
||||
let compressedData = try compressor.compress(data)
|
||||
if compressorTakeOver {
|
||||
try compressor.reset()
|
||||
}
|
||||
return compressedData
|
||||
} catch {
|
||||
//do nothing with the error for now
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Decompressor {
|
||||
private var strm = z_stream()
|
||||
private var buffer = [UInt8](repeating: 0, count: 0x2000)
|
||||
private var inflateInitialized = false
|
||||
private let windowBits: Int
|
||||
|
||||
init?(windowBits: Int) {
|
||||
self.windowBits = windowBits
|
||||
guard initInflate() else { return nil }
|
||||
}
|
||||
|
||||
private func initInflate() -> Bool {
|
||||
if Z_OK == inflateInit2_(&strm, -CInt(windowBits),
|
||||
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
|
||||
{
|
||||
inflateInitialized = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
teardownInflate()
|
||||
guard initInflate() else { throw WSError(type: .compressionError, message: "Error for decompressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func decompress(_ data: Data, finish: Bool) throws -> Data {
|
||||
return try data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return try decompress(bytes: bytes, count: data.count, finish: finish)
|
||||
}
|
||||
}
|
||||
|
||||
func decompress(bytes: UnsafePointer<UInt8>, count: Int, finish: Bool) throws -> Data {
|
||||
var decompressed = Data()
|
||||
try decompress(bytes: bytes, count: count, out: &decompressed)
|
||||
|
||||
if finish {
|
||||
let tail:[UInt8] = [0x00, 0x00, 0xFF, 0xFF]
|
||||
try decompress(bytes: tail, count: tail.count, out: &decompressed)
|
||||
}
|
||||
|
||||
return decompressed
|
||||
}
|
||||
|
||||
private func decompress(bytes: UnsafePointer<UInt8>, count: Int, out: inout Data) throws {
|
||||
var res: CInt = 0
|
||||
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: bytes)
|
||||
strm.avail_in = CUnsignedInt(count)
|
||||
|
||||
repeat {
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = inflate(&strm, 0)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
out.append(buffer, count: byteCount)
|
||||
} while res == Z_OK && strm.avail_out == 0
|
||||
|
||||
guard (res == Z_OK && strm.avail_out > 0)
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw WSError(type: .compressionError, message: "Error on decompressing", code: 0)
|
||||
}
|
||||
}
|
||||
|
||||
private func teardownInflate() {
|
||||
if inflateInitialized, Z_OK == inflateEnd(&strm) {
|
||||
inflateInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
teardownInflate()
|
||||
}
|
||||
}
|
||||
|
||||
class Compressor {
|
||||
private var strm = z_stream()
|
||||
private var buffer = [UInt8](repeating: 0, count: 0x2000)
|
||||
private var deflateInitialized = false
|
||||
private let windowBits: Int
|
||||
|
||||
init?(windowBits: Int) {
|
||||
self.windowBits = windowBits
|
||||
guard initDeflate() else { return nil }
|
||||
}
|
||||
|
||||
private func initDeflate() -> Bool {
|
||||
if Z_OK == deflateInit2_(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
|
||||
-CInt(windowBits), 8, Z_DEFAULT_STRATEGY,
|
||||
ZLIB_VERSION, CInt(MemoryLayout<z_stream>.size))
|
||||
{
|
||||
deflateInitialized = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func reset() throws {
|
||||
teardownDeflate()
|
||||
guard initDeflate() else { throw WSError(type: .compressionError, message: "Error for compressor on reset", code: 0) }
|
||||
}
|
||||
|
||||
func compress(_ data: Data) throws -> Data {
|
||||
var compressed = Data()
|
||||
var res: CInt = 0
|
||||
data.withUnsafeBytes { (ptr:UnsafePointer<UInt8>) -> Void in
|
||||
strm.next_in = UnsafeMutablePointer<UInt8>(mutating: ptr)
|
||||
strm.avail_in = CUnsignedInt(data.count)
|
||||
|
||||
repeat {
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = deflate(&strm, Z_SYNC_FLUSH)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
compressed.append(buffer, count: byteCount)
|
||||
}
|
||||
while res == Z_OK && strm.avail_out == 0
|
||||
|
||||
}
|
||||
|
||||
guard res == Z_OK && strm.avail_out > 0
|
||||
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
|
||||
else {
|
||||
throw WSError(type: .compressionError, message: "Error on compressing", code: 0)
|
||||
}
|
||||
|
||||
compressed.removeLast(4)
|
||||
return compressed
|
||||
}
|
||||
|
||||
private func teardownDeflate() {
|
||||
if deflateInitialized, Z_OK == deflateEnd(&strm) {
|
||||
deflateInitialized = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
teardownDeflate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Data+Extensions.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/27/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// Fix for deprecation warnings
|
||||
//
|
||||
// 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
|
||||
|
||||
internal extension Data {
|
||||
struct ByteError: Swift.Error {}
|
||||
|
||||
#if swift(>=5.0)
|
||||
func withUnsafeBytes<ResultType, ContentType>(_ completion: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
return try withUnsafeBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
} else {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.0)
|
||||
mutating func withUnsafeMutableBytes<ResultType, ContentType>(_ completion: (UnsafeMutablePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
return try withUnsafeMutableBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
} else {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Engine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public protocol EngineDelegate: AnyObject {
|
||||
func didReceive(event: WebSocketEvent)
|
||||
}
|
||||
|
||||
public protocol Engine {
|
||||
func register(delegate: EngineDelegate)
|
||||
func start(request: URLRequest)
|
||||
func stop(closeCode: UInt16)
|
||||
func forceStop()
|
||||
func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?)
|
||||
func write(string: String, completion: (() -> ())?)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// NativeEngine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public class NativeEngine: NSObject, Engine, URLSessionDataDelegate, URLSessionWebSocketDelegate {
|
||||
private var task: URLSessionWebSocketTask?
|
||||
weak var delegate: EngineDelegate?
|
||||
|
||||
public func register(delegate: EngineDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func start(request: URLRequest) {
|
||||
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
||||
task = session.webSocketTask(with: request)
|
||||
doRead()
|
||||
task?.resume()
|
||||
}
|
||||
|
||||
public func stop(closeCode: UInt16) {
|
||||
let closeCode = URLSessionWebSocketTask.CloseCode(rawValue: Int(closeCode)) ?? .normalClosure
|
||||
task?.cancel(with: closeCode, reason: nil)
|
||||
}
|
||||
|
||||
public func forceStop() {
|
||||
stop(closeCode: UInt16(URLSessionWebSocketTask.CloseCode.abnormalClosure.rawValue))
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
task?.send(.string(string), completionHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
switch opcode {
|
||||
case .binaryFrame:
|
||||
task?.send(.data(data), completionHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
case .textFrame:
|
||||
let text = String(data: data, encoding: .utf8)!
|
||||
write(string: text, completion: completion)
|
||||
case .ping:
|
||||
task?.sendPing(pongReceiveHandler: { (error) in
|
||||
completion?()
|
||||
})
|
||||
default:
|
||||
break //unsupported
|
||||
}
|
||||
}
|
||||
|
||||
private func doRead() {
|
||||
task?.receive { [weak self] (result) in
|
||||
switch result {
|
||||
case .success(let message):
|
||||
switch message {
|
||||
case .string(let string):
|
||||
self?.broadcast(event: .text(string))
|
||||
case .data(let data):
|
||||
self?.broadcast(event: .binary(data))
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
break
|
||||
case .failure(let error):
|
||||
self?.broadcast(event: .error(error))
|
||||
return
|
||||
}
|
||||
self?.doRead()
|
||||
}
|
||||
}
|
||||
|
||||
private func broadcast(event: WebSocketEvent) {
|
||||
delegate?.didReceive(event: event)
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
|
||||
let p = `protocol` ?? ""
|
||||
broadcast(event: .connected([HTTPWSHeader.protocolName: p]))
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
|
||||
var r = ""
|
||||
if let d = reason {
|
||||
r = String(data: d, encoding: .utf8) ?? ""
|
||||
}
|
||||
broadcast(event: .disconnected(r, UInt16(closeCode.rawValue)))
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
||||
broadcast(event: .error(error))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WSEngine.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 6/15/19
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class WSEngine: Engine, TransportEventClient, FramerEventClient,
|
||||
FrameCollectorDelegate, HTTPHandlerDelegate {
|
||||
private let transport: Transport
|
||||
private let framer: Framer
|
||||
private let httpHandler: HTTPHandler
|
||||
private let compressionHandler: CompressionHandler?
|
||||
private let certPinner: CertificatePinning?
|
||||
private let headerChecker: HeaderValidator
|
||||
private var request: URLRequest!
|
||||
|
||||
private let frameHandler = FrameCollector()
|
||||
private var didUpgrade = false
|
||||
private var secKeyValue = ""
|
||||
private let writeQueue = DispatchQueue(label: "com.vluxe.starscream.writequeue")
|
||||
private let mutex = DispatchSemaphore(value: 1)
|
||||
private var canSend = false
|
||||
|
||||
weak var delegate: EngineDelegate?
|
||||
public var respondToPingWithPong: Bool = true
|
||||
|
||||
public init(transport: Transport,
|
||||
certPinner: CertificatePinning? = nil,
|
||||
headerValidator: HeaderValidator = FoundationSecurity(),
|
||||
httpHandler: HTTPHandler = FoundationHTTPHandler(),
|
||||
framer: Framer = WSFramer(),
|
||||
compressionHandler: CompressionHandler? = nil) {
|
||||
self.transport = transport
|
||||
self.framer = framer
|
||||
self.httpHandler = httpHandler
|
||||
self.certPinner = certPinner
|
||||
self.headerChecker = headerValidator
|
||||
self.compressionHandler = compressionHandler
|
||||
framer.updateCompression(supports: compressionHandler != nil)
|
||||
frameHandler.delegate = self
|
||||
}
|
||||
|
||||
public func register(delegate: EngineDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func start(request: URLRequest) {
|
||||
mutex.wait()
|
||||
let isConnected = canSend
|
||||
mutex.signal()
|
||||
if isConnected {
|
||||
return
|
||||
}
|
||||
|
||||
self.request = request
|
||||
transport.register(delegate: self)
|
||||
framer.register(delegate: self)
|
||||
httpHandler.register(delegate: self)
|
||||
frameHandler.delegate = self
|
||||
guard let url = request.url else {
|
||||
return
|
||||
}
|
||||
transport.connect(url: url, timeout: request.timeoutInterval, certificatePinning: certPinner)
|
||||
}
|
||||
|
||||
public func stop(closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
let capacity = MemoryLayout<UInt16>.size
|
||||
var pointer = [UInt8](repeating: 0, count: capacity)
|
||||
writeUint16(&pointer, offset: 0, value: closeCode)
|
||||
let payload = Data(bytes: pointer, count: MemoryLayout<UInt16>.size)
|
||||
write(data: payload, opcode: .connectionClose, completion: { [weak self] in
|
||||
self?.reset()
|
||||
self?.forceStop()
|
||||
})
|
||||
}
|
||||
|
||||
public func forceStop() {
|
||||
transport.disconnect()
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
let data = string.data(using: .utf8)!
|
||||
write(data: data, opcode: .textFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
writeQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.mutex.wait()
|
||||
let canWrite = s.canSend
|
||||
s.mutex.signal()
|
||||
if !canWrite {
|
||||
return
|
||||
}
|
||||
|
||||
var isCompressed = false
|
||||
var sendData = data
|
||||
if let compressedData = s.compressionHandler?.compress(data: data) {
|
||||
sendData = compressedData
|
||||
isCompressed = true
|
||||
}
|
||||
|
||||
let frameData = s.framer.createWriteFrame(opcode: opcode, payload: sendData, isCompressed: isCompressed)
|
||||
s.transport.write(data: frameData, completion: {_ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TransportEventClient
|
||||
|
||||
public func connectionChanged(state: ConnectionState) {
|
||||
switch state {
|
||||
case .connected:
|
||||
secKeyValue = HTTPWSHeader.generateWebSocketKey()
|
||||
let wsReq = HTTPWSHeader.createUpgrade(request: request, supportsCompression: framer.supportsCompression(), secKeyValue: secKeyValue)
|
||||
let data = httpHandler.convert(request: wsReq)
|
||||
transport.write(data: data, completion: {_ in })
|
||||
case .waiting:
|
||||
break
|
||||
case .failed(let error):
|
||||
handleError(error)
|
||||
case .viability(let isViable):
|
||||
broadcast(event: .viabilityChanged(isViable))
|
||||
case .shouldReconnect(let status):
|
||||
broadcast(event: .reconnectSuggested(status))
|
||||
case .receive(let data):
|
||||
if didUpgrade {
|
||||
framer.add(data: data)
|
||||
} else {
|
||||
let offset = httpHandler.parse(data: data)
|
||||
if offset > 0 {
|
||||
let extraData = data.subdata(in: offset..<data.endIndex)
|
||||
framer.add(data: extraData)
|
||||
}
|
||||
}
|
||||
case .cancelled:
|
||||
broadcast(event: .cancelled)
|
||||
case .peerClosed:
|
||||
broadcast(event: .peerClosed)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - HTTPHandlerDelegate
|
||||
|
||||
public func didReceiveHTTP(event: HTTPEvent) {
|
||||
switch event {
|
||||
case .success(let headers):
|
||||
if let error = headerChecker.validate(headers: headers, key: secKeyValue) {
|
||||
handleError(error)
|
||||
return
|
||||
}
|
||||
mutex.wait()
|
||||
didUpgrade = true
|
||||
canSend = true
|
||||
mutex.signal()
|
||||
compressionHandler?.load(headers: headers)
|
||||
if let url = request.url {
|
||||
HTTPCookie.cookies(withResponseHeaderFields: headers, for: url).forEach {
|
||||
HTTPCookieStorage.shared.setCookie($0)
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(event: .connected(headers))
|
||||
case .failure(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FramerEventClient
|
||||
|
||||
public func frameProcessed(event: FrameEvent) {
|
||||
switch event {
|
||||
case .frame(let frame):
|
||||
frameHandler.add(frame: frame)
|
||||
case .error(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - FrameCollectorDelegate
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
return compressionHandler?.decompress(data: data, isFinal: isFinal)
|
||||
}
|
||||
|
||||
public func didForm(event: FrameCollector.Event) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
broadcast(event: .text(string))
|
||||
case .binary(let data):
|
||||
broadcast(event: .binary(data))
|
||||
case .pong(let data):
|
||||
broadcast(event: .pong(data))
|
||||
case .ping(let data):
|
||||
broadcast(event: .ping(data))
|
||||
if respondToPingWithPong {
|
||||
write(data: data ?? Data(), opcode: .pong, completion: nil)
|
||||
}
|
||||
case .closed(let reason, let code):
|
||||
broadcast(event: .disconnected(reason, code))
|
||||
stop(closeCode: code)
|
||||
case .error(let error):
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func broadcast(event: WebSocketEvent) {
|
||||
delegate?.didReceive(event: event)
|
||||
}
|
||||
|
||||
//This call can be coming from a lot of different queues/threads.
|
||||
//be aware of that when modifying shared variables
|
||||
private func handleError(_ error: Error?) {
|
||||
if let wsError = error as? WSError {
|
||||
stop(closeCode: wsError.code)
|
||||
} else {
|
||||
stop()
|
||||
}
|
||||
|
||||
delegate?.didReceive(event: .error(error))
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
mutex.wait()
|
||||
canSend = false
|
||||
didUpgrade = false
|
||||
mutex.signal()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/25/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 os(watchOS)
|
||||
public typealias FoundationHTTPHandler = StringHTTPHandler
|
||||
#else
|
||||
public class FoundationHTTPHandler: HTTPHandler {
|
||||
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPHandlerDelegate?
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func convert(request: URLRequest) -> Data {
|
||||
let msg = CFHTTPMessageCreateRequest(kCFAllocatorDefault, request.httpMethod! as CFString,
|
||||
request.url! as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
if let headers = request.allHTTPHeaderFields {
|
||||
for (aKey, aValue) in headers {
|
||||
CFHTTPMessageSetHeaderFieldValue(msg, aKey as CFString, aValue as CFString)
|
||||
}
|
||||
}
|
||||
if let body = request.httpBody {
|
||||
CFHTTPMessageSetBody(msg, body as CFData)
|
||||
}
|
||||
guard let data = CFHTTPMessageCopySerializedMessage(msg) else {
|
||||
return Data()
|
||||
}
|
||||
return data.takeRetainedValue() as Data
|
||||
}
|
||||
|
||||
public func parse(data: Data) -> Int {
|
||||
let offset = findEndOfHTTP(data: data)
|
||||
if offset > 0 {
|
||||
buffer.append(data.subdata(in: 0..<offset))
|
||||
} else {
|
||||
buffer.append(data)
|
||||
}
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue()
|
||||
if !CFHTTPMessageAppendBytes(response, pointer, data.count) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if !CFHTTPMessageIsHeaderComplete(response) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let nsHeaders = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
var headers = [String: String]()
|
||||
for (key, value) in nsHeaders {
|
||||
if let key = key as? String, let value = value as? String {
|
||||
headers[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
let code = CFHTTPMessageGetResponseStatusCode(response)
|
||||
if code != HTTPWSHeader.switchProtocolCode {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.notAnUpgrade(code, headers)))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceiveHTTP(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
|
||||
public func register(delegate: HTTPHandlerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func findEndOfHTTP(data: Data) -> Int {
|
||||
let endBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
var k = 0
|
||||
for i in 0..<data.count {
|
||||
if pointer[i] == endBytes[k] {
|
||||
k += 1
|
||||
if k == 4 {
|
||||
return i + 1
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/2/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class FoundationHTTPServerHandler: HTTPServerHandler {
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPServerDelegate?
|
||||
let getVerb: NSString = "GET"
|
||||
|
||||
public func register(delegate: HTTPServerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func createResponse(headers: [String: String]) -> Data {
|
||||
#if os(watchOS)
|
||||
//TODO: build response header
|
||||
return Data()
|
||||
#else
|
||||
let response = CFHTTPMessageCreateResponse(kCFAllocatorDefault, HTTPWSHeader.switchProtocolCode,
|
||||
nil, kCFHTTPVersion1_1).takeRetainedValue()
|
||||
|
||||
//TODO: add other values to make a proper response here...
|
||||
//TODO: also sec key thing (Sec-WebSocket-Key)
|
||||
for (key, value) in headers {
|
||||
CFHTTPMessageSetHeaderFieldValue(response, key as CFString, value as CFString)
|
||||
}
|
||||
guard let cfData = CFHTTPMessageCopySerializedMessage(response)?.takeRetainedValue() else {
|
||||
return Data()
|
||||
}
|
||||
return cfData as Data
|
||||
#endif
|
||||
}
|
||||
|
||||
public func parse(data: Data) {
|
||||
buffer.append(data)
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
#if os(watchOS)
|
||||
//TODO: parse data
|
||||
return false
|
||||
#else
|
||||
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true).takeRetainedValue()
|
||||
if !CFHTTPMessageAppendBytes(response, pointer, data.count) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if !CFHTTPMessageIsHeaderComplete(response) {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if let method = CFHTTPMessageCopyRequestMethod(response)?.takeRetainedValue() {
|
||||
if (method as NSString) != getVerb {
|
||||
delegate?.didReceive(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
|
||||
let nsHeaders = cfHeaders.takeRetainedValue() as NSDictionary
|
||||
var headers = [String: String]()
|
||||
for (key, value) in nsHeaders {
|
||||
if let key = key as? String, let value = value as? String {
|
||||
headers[key] = value
|
||||
}
|
||||
}
|
||||
delegate?.didReceive(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceive(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FrameCollector.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/24/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public protocol FrameCollectorDelegate: AnyObject {
|
||||
func didForm(event: FrameCollector.Event)
|
||||
func decompress(data: Data, isFinal: Bool) -> Data?
|
||||
}
|
||||
|
||||
public class FrameCollector {
|
||||
public enum Event {
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error)
|
||||
case closed(String, UInt16)
|
||||
}
|
||||
weak var delegate: FrameCollectorDelegate?
|
||||
var buffer = Data()
|
||||
var frameCount = 0
|
||||
var isText = false //was the first frame a text frame or a binary frame?
|
||||
var needsDecompression = false
|
||||
|
||||
public func add(frame: Frame) {
|
||||
//check single frame action and out of order frames
|
||||
if frame.opcode == .connectionClose {
|
||||
var code = frame.closeCode
|
||||
var reason = "connection closed by server"
|
||||
if let customCloseReason = String(data: frame.payload, encoding: .utf8) {
|
||||
reason = customCloseReason
|
||||
} else {
|
||||
code = CloseCode.protocolError.rawValue
|
||||
}
|
||||
delegate?.didForm(event: .closed(reason, code))
|
||||
return
|
||||
} else if frame.opcode == .pong {
|
||||
delegate?.didForm(event: .pong(frame.payload))
|
||||
return
|
||||
} else if frame.opcode == .ping {
|
||||
delegate?.didForm(event: .ping(frame.payload))
|
||||
return
|
||||
} else if frame.opcode == .continueFrame && frameCount == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "first frame can't be a continue frame", code: errCode)))
|
||||
reset()
|
||||
return
|
||||
} else if frameCount > 0 && frame.opcode != .continueFrame {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "second and beyond of fragment message must be a continue frame", code: errCode)))
|
||||
reset()
|
||||
return
|
||||
}
|
||||
if frameCount == 0 {
|
||||
isText = frame.opcode == .textFrame
|
||||
needsDecompression = frame.needsDecompression
|
||||
}
|
||||
|
||||
let payload: Data
|
||||
if needsDecompression {
|
||||
payload = delegate?.decompress(data: frame.payload, isFinal: frame.isFin) ?? frame.payload
|
||||
} else {
|
||||
payload = frame.payload
|
||||
}
|
||||
buffer.append(payload)
|
||||
frameCount += 1
|
||||
|
||||
if frame.isFin {
|
||||
if isText {
|
||||
if let string = String(data: buffer, encoding: .utf8) {
|
||||
delegate?.didForm(event: .text(string))
|
||||
} else {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "not valid UTF-8 data", code: errCode)))
|
||||
}
|
||||
} else {
|
||||
delegate?.didForm(event: .binary(buffer))
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
buffer = Data()
|
||||
frameCount = 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Framer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
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 MaxFrameSize: Int = 32
|
||||
|
||||
// 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 FrameOpCode: UInt8 {
|
||||
case continueFrame = 0x0
|
||||
case textFrame = 0x1
|
||||
case binaryFrame = 0x2
|
||||
// 3-7 are reserved.
|
||||
case connectionClose = 0x8
|
||||
case ping = 0x9
|
||||
case pong = 0xA
|
||||
// B-F reserved.
|
||||
case unknown = 100
|
||||
}
|
||||
|
||||
public struct Frame {
|
||||
let isFin: Bool
|
||||
let needsDecompression: Bool
|
||||
let isMasked: Bool
|
||||
let opcode: FrameOpCode
|
||||
let payloadLength: UInt64
|
||||
let payload: Data
|
||||
let closeCode: UInt16 //only used by connectionClose opcode
|
||||
}
|
||||
|
||||
public enum FrameEvent {
|
||||
case frame(Frame)
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
public protocol FramerEventClient: AnyObject {
|
||||
func frameProcessed(event: FrameEvent)
|
||||
}
|
||||
|
||||
public protocol Framer {
|
||||
func add(data: Data)
|
||||
func register(delegate: FramerEventClient)
|
||||
func createWriteFrame(opcode: FrameOpCode, payload: Data, isCompressed: Bool) -> Data
|
||||
func updateCompression(supports: Bool)
|
||||
func supportsCompression() -> Bool
|
||||
}
|
||||
|
||||
public class WSFramer: Framer {
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.wsframer", attributes: [])
|
||||
private weak var delegate: FramerEventClient?
|
||||
private var buffer = Data()
|
||||
public var compressionEnabled = false
|
||||
private let isServer: Bool
|
||||
|
||||
public init(isServer: Bool = false) {
|
||||
self.isServer = isServer
|
||||
}
|
||||
|
||||
public func updateCompression(supports: Bool) {
|
||||
compressionEnabled = supports
|
||||
}
|
||||
|
||||
public func supportsCompression() -> Bool {
|
||||
return compressionEnabled
|
||||
}
|
||||
|
||||
enum ProcessEvent {
|
||||
case needsMoreData
|
||||
case processedFrame(Frame, Int)
|
||||
case failed(Error)
|
||||
}
|
||||
|
||||
public func add(data: Data) {
|
||||
queue.async { [weak self] in
|
||||
self?.buffer.append(data)
|
||||
while(true) {
|
||||
let event = self?.process() ?? .needsMoreData
|
||||
switch event {
|
||||
case .needsMoreData:
|
||||
return
|
||||
case .processedFrame(let frame, let split):
|
||||
guard let s = self else { return }
|
||||
s.delegate?.frameProcessed(event: .frame(frame))
|
||||
if split >= s.buffer.count {
|
||||
s.buffer = Data()
|
||||
return
|
||||
}
|
||||
s.buffer = s.buffer.advanced(by: split)
|
||||
case .failed(let error):
|
||||
self?.delegate?.frameProcessed(event: .error(error))
|
||||
self?.buffer = Data()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func register(delegate: FramerEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func process() -> ProcessEvent {
|
||||
if buffer.count < 2 {
|
||||
return .needsMoreData
|
||||
}
|
||||
var pointer = [UInt8]()
|
||||
buffer.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
|
||||
let isFin = (FinMask & pointer[0])
|
||||
let opcodeRawValue = (OpCodeMask & pointer[0])
|
||||
let opcode = FrameOpCode(rawValue: opcodeRawValue) ?? .unknown
|
||||
let isMasked = (MaskMask & pointer[1])
|
||||
let payloadLen = (PayloadLenMask & pointer[1])
|
||||
let RSV1 = (RSVMask & pointer[0])
|
||||
var needsDecompression = false
|
||||
|
||||
if compressionEnabled && opcode != .continueFrame {
|
||||
needsDecompression = (RSV1Mask & pointer[0]) > 0
|
||||
}
|
||||
if !isServer && (isMasked > 0 || RSV1 > 0) && opcode != .pong && !needsDecompression {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "masked and rsv data is not currently supported", code: errCode))
|
||||
}
|
||||
let isControlFrame = (opcode == .connectionClose || opcode == .ping || opcode == .pong)
|
||||
if !isControlFrame && (opcode != .binaryFrame && opcode != .continueFrame &&
|
||||
opcode != .textFrame && opcode != .pong) {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "unknown opcode: \(opcodeRawValue)", code: errCode))
|
||||
}
|
||||
if isControlFrame && isFin == 0 {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
return .failed(WSError(type: .protocolError, message: "control frames can't be fragmented", code: errCode))
|
||||
}
|
||||
|
||||
var offset = 2
|
||||
|
||||
if isControlFrame && payloadLen > 125 {
|
||||
return .failed(WSError(type: .protocolError, message: "payload length is longer than allowed for a control frame", code: CloseCode.protocolError.rawValue))
|
||||
}
|
||||
|
||||
var dataLength = UInt64(payloadLen)
|
||||
var closeCode = CloseCode.normal.rawValue
|
||||
if opcode == .connectionClose {
|
||||
if payloadLen == 1 {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
dataLength = 0
|
||||
} else if payloadLen > 1 {
|
||||
if pointer.count < 4 {
|
||||
return .needsMoreData
|
||||
}
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
closeCode = pointer.readUint16(offset: offset)
|
||||
offset += size
|
||||
dataLength -= UInt64(size)
|
||||
if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1013 && closeCode < 3000) {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if payloadLen == 127 {
|
||||
let size = MemoryLayout<UInt64>.size
|
||||
if size + offset > pointer.count {
|
||||
return .needsMoreData
|
||||
}
|
||||
dataLength = pointer.readUint64(offset: offset)
|
||||
offset += size
|
||||
} else if payloadLen == 126 {
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
if size + offset > pointer.count {
|
||||
return .needsMoreData
|
||||
}
|
||||
dataLength = UInt64(pointer.readUint16(offset: offset))
|
||||
offset += size
|
||||
}
|
||||
|
||||
let maskStart = offset
|
||||
if isServer {
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
}
|
||||
|
||||
if dataLength > (pointer.count - offset) {
|
||||
return .needsMoreData
|
||||
}
|
||||
|
||||
//I don't like this cast, but Data's count returns an Int.
|
||||
//Might be a problem with huge payloads. Need to revisit.
|
||||
let readDataLength = Int(dataLength)
|
||||
|
||||
let payload: Data
|
||||
if readDataLength == 0 {
|
||||
payload = Data()
|
||||
} else {
|
||||
if isServer {
|
||||
payload = pointer.unmaskData(maskStart: maskStart, offset: offset, length: readDataLength)
|
||||
} else {
|
||||
let end = offset + readDataLength
|
||||
payload = Data(pointer[offset..<end])
|
||||
}
|
||||
}
|
||||
offset += readDataLength
|
||||
|
||||
let frame = Frame(isFin: isFin > 0, needsDecompression: needsDecompression, isMasked: isMasked > 0, opcode: opcode, payloadLength: dataLength, payload: payload, closeCode: closeCode)
|
||||
return .processedFrame(frame, offset)
|
||||
}
|
||||
|
||||
public func createWriteFrame(opcode: FrameOpCode, payload: Data, isCompressed: Bool) -> Data {
|
||||
let payloadLength = payload.count
|
||||
|
||||
let capacity = payloadLength + MaxFrameSize
|
||||
var pointer = [UInt8](repeating: 0, count: capacity)
|
||||
|
||||
//set the framing info
|
||||
pointer[0] = FinMask | opcode.rawValue
|
||||
if isCompressed {
|
||||
pointer[0] |= RSV1Mask
|
||||
}
|
||||
|
||||
var offset = 2 //skip pass the framing info
|
||||
if payloadLength < 126 {
|
||||
pointer[1] = UInt8(payloadLength)
|
||||
} else if payloadLength <= Int(UInt16.max) {
|
||||
pointer[1] = 126
|
||||
writeUint16(&pointer, offset: offset, value: UInt16(payloadLength))
|
||||
offset += MemoryLayout<UInt16>.size
|
||||
} else {
|
||||
pointer[1] = 127
|
||||
writeUint64(&pointer, offset: offset, value: UInt64(payloadLength))
|
||||
offset += MemoryLayout<UInt64>.size
|
||||
}
|
||||
|
||||
//clients are required to mask the payload data, but server don't according to the RFC
|
||||
if !isServer {
|
||||
pointer[1] |= MaskMask
|
||||
|
||||
//write the random mask key in
|
||||
let maskKey: UInt32 = UInt32.random(in: 0...UInt32.max)
|
||||
|
||||
writeUint32(&pointer, offset: offset, value: maskKey)
|
||||
let maskStart = offset
|
||||
offset += MemoryLayout<UInt32>.size
|
||||
|
||||
//now write the payload data in
|
||||
for i in 0..<payloadLength {
|
||||
pointer[offset] = payload[i] ^ pointer[maskStart + (i % MemoryLayout<UInt32>.size)]
|
||||
offset += 1
|
||||
}
|
||||
} else {
|
||||
for i in 0..<payloadLength {
|
||||
pointer[offset] = payload[i]
|
||||
offset += 1
|
||||
}
|
||||
}
|
||||
return Data(pointer[0..<offset])
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - functions for simpler array buffer reading and writing
|
||||
|
||||
public protocol MyWSArrayType {}
|
||||
extension UInt8: MyWSArrayType {}
|
||||
|
||||
public extension Array where Element: MyWSArrayType & UnsignedInteger {
|
||||
|
||||
/**
|
||||
Read a UInt16 from a buffer.
|
||||
- parameter offset: is the offset index to start the read from (e.g. buffer[0], buffer[1], etc).
|
||||
- returns: a UInt16 of the value from the buffer
|
||||
*/
|
||||
func readUint16(offset: Int) -> UInt16 {
|
||||
return (UInt16(self[offset + 0]) << 8) | UInt16(self[offset + 1])
|
||||
}
|
||||
|
||||
/**
|
||||
Read a UInt64 from a buffer.
|
||||
- parameter offset: is the offset index to start the read from (e.g. buffer[0], buffer[1], etc).
|
||||
- returns: a UInt64 of the value from the buffer
|
||||
*/
|
||||
func readUint64(offset: Int) -> UInt64 {
|
||||
var value = UInt64(0)
|
||||
for i in 0...7 {
|
||||
value = (value << 8) | UInt64(self[offset + i])
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func unmaskData(maskStart: Int, offset: Int, length: Int) -> Data {
|
||||
var unmaskedBytes = [UInt8](repeating: 0, count: length)
|
||||
let maskSize = MemoryLayout<UInt32>.size
|
||||
for i in 0..<length {
|
||||
unmaskedBytes[i] = UInt8(self[offset + i] ^ self[maskStart + (i % maskSize)])
|
||||
}
|
||||
return Data(unmaskedBytes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt16 to the buffer. It fills the 2 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint16( _ buffer: inout [UInt8], offset: Int, value: UInt16) {
|
||||
buffer[offset + 0] = UInt8(value >> 8)
|
||||
buffer[offset + 1] = UInt8(value & 0xff)
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt32 to the buffer. It fills the 4 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint32( _ buffer: inout [UInt8], offset: Int, value: UInt32) {
|
||||
for i in 0...3 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt32(3 - i))) & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Write a UInt64 to the buffer. It fills the 8 array "slots" of the UInt8 array.
|
||||
- parameter buffer: is the UInt8 array (pointer) to write the value too.
|
||||
- parameter offset: is the offset index to start the write from (e.g. buffer[0], buffer[1], etc).
|
||||
*/
|
||||
public func writeUint64( _ buffer: inout [UInt8], offset: Int, value: UInt64) {
|
||||
for i in 0...7 {
|
||||
buffer[offset + i] = UInt8((value >> (8*UInt64(7 - i))) & 0xff)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/24/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum HTTPUpgradeError: Error {
|
||||
case notAnUpgrade(Int, [String: String])
|
||||
case invalidData
|
||||
}
|
||||
|
||||
public struct HTTPWSHeader {
|
||||
static let upgradeName = "Upgrade"
|
||||
static let upgradeValue = "websocket"
|
||||
static let hostName = "Host"
|
||||
static let connectionName = "Connection"
|
||||
static let connectionValue = "Upgrade"
|
||||
static let protocolName = "Sec-WebSocket-Protocol"
|
||||
static let versionName = "Sec-WebSocket-Version"
|
||||
static let versionValue = "13"
|
||||
static let extensionName = "Sec-WebSocket-Extensions"
|
||||
static let keyName = "Sec-WebSocket-Key"
|
||||
static let originName = "Origin"
|
||||
static let acceptName = "Sec-WebSocket-Accept"
|
||||
static let switchProtocolCode = 101
|
||||
static let defaultSSLSchemes = ["wss", "https"]
|
||||
|
||||
/// Creates a new URLRequest based off the source URLRequest.
|
||||
/// - Parameter request: the request to "upgrade" the WebSocket request by adding headers.
|
||||
/// - Parameter supportsCompression: set if the client support text compression.
|
||||
/// - Parameter secKeyName: the security key to use in the WebSocket request. https://tools.ietf.org/html/rfc6455#section-1.3
|
||||
/// - returns: A URLRequest request to be converted to data and sent to the server.
|
||||
public static func createUpgrade(request: URLRequest, supportsCompression: Bool, secKeyValue: String) -> URLRequest {
|
||||
guard let url = request.url, let parts = url.getParts() else {
|
||||
return request
|
||||
}
|
||||
|
||||
var req = request
|
||||
if request.value(forHTTPHeaderField: HTTPWSHeader.originName) == nil {
|
||||
var origin = url.absoluteString
|
||||
if let hostUrl = URL (string: "/", relativeTo: url) {
|
||||
origin = hostUrl.absoluteString
|
||||
origin.remove(at: origin.index(before: origin.endIndex))
|
||||
}
|
||||
req.setValue(origin, forHTTPHeaderField: HTTPWSHeader.originName)
|
||||
}
|
||||
req.setValue(HTTPWSHeader.upgradeValue, forHTTPHeaderField: HTTPWSHeader.upgradeName)
|
||||
req.setValue(HTTPWSHeader.connectionValue, forHTTPHeaderField: HTTPWSHeader.connectionName)
|
||||
req.setValue(HTTPWSHeader.versionValue, forHTTPHeaderField: HTTPWSHeader.versionName)
|
||||
req.setValue(secKeyValue, forHTTPHeaderField: HTTPWSHeader.keyName)
|
||||
|
||||
if req.allHTTPHeaderFields?["Cookie"] == nil {
|
||||
if let cookies = HTTPCookieStorage.shared.cookies(for: url), !cookies.isEmpty {
|
||||
let headers = HTTPCookie.requestHeaderFields(with: cookies)
|
||||
for (key, val) in headers {
|
||||
req.setValue(val, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if supportsCompression {
|
||||
let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
|
||||
req.setValue(val, forHTTPHeaderField: HTTPWSHeader.extensionName)
|
||||
}
|
||||
let hostValue = req.allHTTPHeaderFields?[HTTPWSHeader.hostName] ?? "\(parts.host):\(parts.port)"
|
||||
req.setValue(hostValue, forHTTPHeaderField: HTTPWSHeader.hostName)
|
||||
return req
|
||||
}
|
||||
|
||||
// generateWebSocketKey 16 random characters between a-z and return them as a base64 string
|
||||
public static func generateWebSocketKey() -> String {
|
||||
return Data((0..<16).map{ _ in UInt8.random(in: 97...122) }).base64EncodedString()
|
||||
}
|
||||
}
|
||||
|
||||
public enum HTTPEvent {
|
||||
case success([String: String])
|
||||
case failure(Error)
|
||||
}
|
||||
|
||||
public protocol HTTPHandlerDelegate: AnyObject {
|
||||
func didReceiveHTTP(event: HTTPEvent)
|
||||
}
|
||||
|
||||
public protocol HTTPHandler {
|
||||
func register(delegate: HTTPHandlerDelegate)
|
||||
func convert(request: URLRequest) -> Data
|
||||
func parse(data: Data) -> Int
|
||||
}
|
||||
|
||||
public protocol HTTPServerDelegate: AnyObject {
|
||||
func didReceive(event: HTTPEvent)
|
||||
}
|
||||
|
||||
public protocol HTTPServerHandler {
|
||||
func register(delegate: HTTPServerDelegate)
|
||||
func parse(data: Data)
|
||||
func createResponse(headers: [String: String]) -> Data
|
||||
}
|
||||
|
||||
public struct URLParts {
|
||||
let port: Int
|
||||
let host: String
|
||||
let isTLS: Bool
|
||||
}
|
||||
|
||||
public extension URL {
|
||||
/// isTLSScheme returns true if the scheme is https or wss
|
||||
var isTLSScheme: Bool {
|
||||
guard let scheme = self.scheme else {
|
||||
return false
|
||||
}
|
||||
return HTTPWSHeader.defaultSSLSchemes.contains(scheme)
|
||||
}
|
||||
|
||||
/// getParts pulls host and port from the url.
|
||||
func getParts() -> URLParts? {
|
||||
guard let host = self.host else {
|
||||
return nil // no host, this isn't a valid url
|
||||
}
|
||||
let isTLS = isTLSScheme
|
||||
var port = self.port ?? 0
|
||||
if self.port == nil {
|
||||
if isTLS {
|
||||
port = 443
|
||||
} else {
|
||||
port = 80
|
||||
}
|
||||
}
|
||||
return URLParts(port: port, host: host, isTLS: isTLS)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// StringHTTPHandler.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 8/25/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public class StringHTTPHandler: HTTPHandler {
|
||||
|
||||
var buffer = Data()
|
||||
weak var delegate: HTTPHandlerDelegate?
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func convert(request: URLRequest) -> Data {
|
||||
guard let url = request.url else {
|
||||
return Data()
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
guard var data = httpBody.data(using: .utf8) else {
|
||||
return Data()
|
||||
}
|
||||
|
||||
if let body = request.httpBody {
|
||||
data.append(body)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
public func parse(data: Data) -> Int {
|
||||
let offset = findEndOfHTTP(data: data)
|
||||
if offset > 0 {
|
||||
buffer.append(data.subdata(in: 0..<offset))
|
||||
if parseContent(data: buffer) {
|
||||
buffer = Data()
|
||||
}
|
||||
} else {
|
||||
buffer.append(data)
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
//returns true when the buffer should be cleared
|
||||
func parseContent(data: Data) -> Bool {
|
||||
guard let str = String(data: data, encoding: .utf8) else {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
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 {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
if let c = Int(responseSplit[1]) {
|
||||
code = c
|
||||
}
|
||||
} else {
|
||||
guard let separatorIndex = str.firstIndex(of: ":") else { break }
|
||||
let key = str.prefix(upTo: separatorIndex).trimmingCharacters(in: .whitespaces)
|
||||
let val = str.suffix(from: str.index(after: separatorIndex)).trimmingCharacters(in: .whitespaces)
|
||||
headers[key.lowercased()] = val
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
if code != HTTPWSHeader.switchProtocolCode {
|
||||
delegate?.didReceiveHTTP(event: .failure(HTTPUpgradeError.notAnUpgrade(code, headers)))
|
||||
return true
|
||||
}
|
||||
|
||||
delegate?.didReceiveHTTP(event: .success(headers))
|
||||
return true
|
||||
}
|
||||
|
||||
public func register(delegate: HTTPHandlerDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
private func findEndOfHTTP(data: Data) -> Int {
|
||||
let endBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
|
||||
var pointer = [UInt8]()
|
||||
data.withUnsafeBytes { pointer.append(contentsOf: $0) }
|
||||
var k = 0
|
||||
for i in 0..<data.count {
|
||||
if pointer[i] == endBytes[k] {
|
||||
k += 1
|
||||
if k == 4 {
|
||||
return i + 1
|
||||
}
|
||||
} else {
|
||||
k = 0
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -0,0 +1,99 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationSecurity.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/16/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 CommonCrypto
|
||||
|
||||
public enum FoundationSecurityError: Error {
|
||||
case invalidRequest
|
||||
}
|
||||
|
||||
public class FoundationSecurity {
|
||||
var allowSelfSigned = false
|
||||
|
||||
public init(allowSelfSigned: Bool = false) {
|
||||
self.allowSelfSigned = allowSelfSigned
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension FoundationSecurity: CertificatePinning {
|
||||
public func evaluateTrust(trust: SecTrust, domain: String?, completion: ((PinningState) -> ())) {
|
||||
if allowSelfSigned {
|
||||
completion(.success)
|
||||
return
|
||||
}
|
||||
|
||||
SecTrustSetPolicies(trust, SecPolicyCreateSSL(true, domain as NSString?))
|
||||
|
||||
handleSecurityTrust(trust: trust, completion: completion)
|
||||
}
|
||||
|
||||
private func handleSecurityTrust(trust: SecTrust, completion: ((PinningState) -> ())) {
|
||||
if #available(iOS 12.0, OSX 10.14, watchOS 5.0, tvOS 12.0, *) {
|
||||
var error: CFError?
|
||||
if SecTrustEvaluateWithError(trust, &error) {
|
||||
completion(.success)
|
||||
} else {
|
||||
completion(.failed(error))
|
||||
}
|
||||
} else {
|
||||
handleOldSecurityTrust(trust: trust, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleOldSecurityTrust(trust: SecTrust, completion: ((PinningState) -> ())) {
|
||||
var result: SecTrustResultType = .unspecified
|
||||
SecTrustEvaluate(trust, &result)
|
||||
if result == .unspecified || result == .proceed {
|
||||
completion(.success)
|
||||
} else {
|
||||
let e = CFErrorCreate(kCFAllocatorDefault, "FoundationSecurityError" as NSString?, Int(result.rawValue), nil)
|
||||
completion(.failed(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension FoundationSecurity: HeaderValidator {
|
||||
public func validate(headers: [String: String], key: String) -> Error? {
|
||||
if let acceptKey = headers[HTTPWSHeader.acceptName] {
|
||||
let sha = "\(key)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64()
|
||||
if sha != acceptKey {
|
||||
return WSError(type: .securityError, message: "accept header doesn't match", code: SecurityErrorCode.acceptFailed.rawValue)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
func sha1Base64() -> String {
|
||||
let data = self.data(using: .utf8)!
|
||||
let pointer = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
|
||||
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
|
||||
CC_SHA1(bytes.baseAddress, CC_LONG(data.count), &digest)
|
||||
return digest
|
||||
}
|
||||
return Data(pointer).base64EncodedString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Security.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 3/16/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum SecurityErrorCode: UInt16 {
|
||||
case acceptFailed = 1
|
||||
case pinningFailed = 2
|
||||
}
|
||||
|
||||
public enum PinningState {
|
||||
case success
|
||||
case failed(CFError?)
|
||||
}
|
||||
|
||||
// CertificatePinning protocol provides an interface for Transports to handle Certificate
|
||||
// or Public Key Pinning.
|
||||
public protocol CertificatePinning: AnyObject {
|
||||
func evaluateTrust(trust: SecTrust, domain: String?, completion: ((PinningState) -> ()))
|
||||
}
|
||||
|
||||
// validates the "Sec-WebSocket-Accept" header as defined 1.3 of the RFC 6455
|
||||
// https://tools.ietf.org/html/rfc6455#section-1.3
|
||||
public protocol HeaderValidator: AnyObject {
|
||||
func validate(headers: [String: String], key: String) -> Error?
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Server.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/2/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum ConnectionEvent {
|
||||
case connected([String: String])
|
||||
case disconnected(String, UInt16)
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
public protocol Connection {
|
||||
func write(data: Data, opcode: FrameOpCode)
|
||||
}
|
||||
|
||||
public protocol ConnectionDelegate: AnyObject {
|
||||
func didReceive(event: ServerEvent)
|
||||
}
|
||||
|
||||
public enum ServerEvent {
|
||||
case connected(Connection, [String: String])
|
||||
case disconnected(Connection, String, UInt16)
|
||||
case text(Connection, String)
|
||||
case binary(Connection, Data)
|
||||
case pong(Connection, Data?)
|
||||
case ping(Connection, Data?)
|
||||
}
|
||||
|
||||
public protocol Server {
|
||||
func start(address: String, port: UInt16) -> Error?
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WebSocketServer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 4/5/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if canImport(Network)
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
/// WebSocketServer is a Network.framework implementation of a WebSocket server
|
||||
@available(watchOS, unavailable)
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class WebSocketServer: Server, ConnectionDelegate {
|
||||
public var onEvent: ((ServerEvent) -> Void)?
|
||||
private var connections = [String: ServerConnection]()
|
||||
private var listener: NWListener?
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.server.networkstream", attributes: [])
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func start(address: String, port: UInt16) -> Error? {
|
||||
//TODO: support TLS cert adding/binding
|
||||
let parameters = NWParameters(tls: nil, tcp: NWProtocolTCP.Options())
|
||||
let p = NWEndpoint.Port(rawValue: port)!
|
||||
parameters.requiredLocalEndpoint = NWEndpoint.hostPort(host: NWEndpoint.Host.name(address, nil), port: p)
|
||||
|
||||
guard let listener = try? NWListener(using: parameters, on: p) else {
|
||||
return WSError(type: .serverError, message: "unable to start the listener at: \(address):\(port)", code: 0)
|
||||
}
|
||||
listener.newConnectionHandler = {[weak self] conn in
|
||||
let transport = TCPTransport(connection: conn)
|
||||
let c = ServerConnection(transport: transport)
|
||||
c.delegate = self
|
||||
self?.connections[c.uuid] = c
|
||||
}
|
||||
// listener.stateUpdateHandler = { state in
|
||||
// switch state {
|
||||
// case .ready:
|
||||
// print("ready to get sockets!")
|
||||
// case .setup:
|
||||
// print("setup to get sockets!")
|
||||
// case .cancelled:
|
||||
// print("server cancelled!")
|
||||
// case .waiting(let error):
|
||||
// print("waiting error: \(error)")
|
||||
// case .failed(let error):
|
||||
// print("server failed: \(error)")
|
||||
// @unknown default:
|
||||
// print("wat?")
|
||||
// }
|
||||
// }
|
||||
self.listener = listener
|
||||
listener.start(queue: queue)
|
||||
return nil
|
||||
}
|
||||
|
||||
public func didReceive(event: ServerEvent) {
|
||||
onEvent?(event)
|
||||
switch event {
|
||||
case .disconnected(let conn, _, _):
|
||||
guard let conn = conn as? ServerConnection else {
|
||||
return
|
||||
}
|
||||
connections.removeValue(forKey: conn.uuid)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class ServerConnection: Connection, HTTPServerDelegate, FramerEventClient, FrameCollectorDelegate, TransportEventClient {
|
||||
let transport: TCPTransport
|
||||
private let httpHandler = FoundationHTTPServerHandler()
|
||||
private let framer = WSFramer(isServer: true)
|
||||
private let frameHandler = FrameCollector()
|
||||
private var didUpgrade = false
|
||||
public var onEvent: ((ConnectionEvent) -> Void)?
|
||||
public weak var delegate: ConnectionDelegate?
|
||||
private let id: String
|
||||
var uuid: String {
|
||||
return id
|
||||
}
|
||||
|
||||
init(transport: TCPTransport) {
|
||||
self.id = UUID().uuidString
|
||||
self.transport = transport
|
||||
transport.register(delegate: self)
|
||||
httpHandler.register(delegate: self)
|
||||
framer.register(delegate: self)
|
||||
frameHandler.delegate = self
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode) {
|
||||
let wsData = framer.createWriteFrame(opcode: opcode, payload: data, isCompressed: false)
|
||||
transport.write(data: wsData, completion: {_ in })
|
||||
}
|
||||
|
||||
// MARK: - TransportEventClient
|
||||
|
||||
public func connectionChanged(state: ConnectionState) {
|
||||
switch state {
|
||||
case .connected:
|
||||
break
|
||||
case .waiting:
|
||||
break
|
||||
case .failed(let error):
|
||||
print("server connection error: \(error ?? WSError(type: .protocolError, message: "default error, no extra data", code: 0))") //handleError(error)
|
||||
case .viability(_):
|
||||
break
|
||||
case .shouldReconnect(_):
|
||||
break
|
||||
case .receive(let data):
|
||||
if didUpgrade {
|
||||
framer.add(data: data)
|
||||
} else {
|
||||
httpHandler.parse(data: data)
|
||||
}
|
||||
case .cancelled:
|
||||
print("server connection cancelled!")
|
||||
//broadcast(event: .cancelled)
|
||||
case .peerClosed:
|
||||
delegate?.didReceive(event: .disconnected(self, "Connection closed by peer", UInt16(FrameOpCode.connectionClose.rawValue)))
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - HTTPServerDelegate
|
||||
|
||||
public func didReceive(event: HTTPEvent) {
|
||||
switch event {
|
||||
case .success(let headers):
|
||||
didUpgrade = true
|
||||
let response = httpHandler.createResponse(headers: [:])
|
||||
transport.write(data: response, completion: {_ in })
|
||||
delegate?.didReceive(event: .connected(self, headers))
|
||||
onEvent?(.connected(headers))
|
||||
case .failure(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - FrameCollectorDelegate
|
||||
|
||||
public func frameProcessed(event: FrameEvent) {
|
||||
switch event {
|
||||
case .frame(let frame):
|
||||
frameHandler.add(frame: frame)
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func didForm(event: FrameCollector.Event) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
delegate?.didReceive(event: .text(self, string))
|
||||
onEvent?(.text(string))
|
||||
case .binary(let data):
|
||||
delegate?.didReceive(event: .binary(self, data))
|
||||
onEvent?(.binary(data))
|
||||
case .pong(let data):
|
||||
delegate?.didReceive(event: .pong(self, data))
|
||||
onEvent?(.pong(data))
|
||||
case .ping(let data):
|
||||
delegate?.didReceive(event: .ping(self, data))
|
||||
onEvent?(.ping(data))
|
||||
case .closed(let reason, let code):
|
||||
delegate?.didReceive(event: .disconnected(self, reason, code))
|
||||
onEvent?(.disconnected(reason, code))
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Starscream.h
|
||||
// Starscream
|
||||
//
|
||||
// Created by Austin Cherry on 9/25/14.
|
||||
// Copyright © 2014 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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/Foundation.h>
|
||||
|
||||
//! Project version number for Starscream.
|
||||
FOUNDATION_EXPORT double StarscreamVersionNumber;
|
||||
|
||||
//! Project version string for Starscream.
|
||||
FOUNDATION_EXPORT const unsigned char StarscreamVersionString[];
|
||||
|
||||
// In this header, you should import all the public headers of your framework using statements like #import <Starscream/PublicHeader.h>
|
||||
@@ -0,0 +1,174 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Websocket.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 7/16/14.
|
||||
// Copyright (c) 2014-2019 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
|
||||
|
||||
public enum ErrorType: Error {
|
||||
case compressionError
|
||||
case securityError
|
||||
case protocolError //There was an error parsing the WebSocket frames
|
||||
case serverError
|
||||
}
|
||||
|
||||
public struct WSError: Error {
|
||||
public let type: ErrorType
|
||||
public let message: String
|
||||
public let code: UInt16
|
||||
|
||||
public init(type: ErrorType, message: String, code: UInt16) {
|
||||
self.type = type
|
||||
self.message = message
|
||||
self.code = code
|
||||
}
|
||||
}
|
||||
|
||||
public protocol WebSocketClient: AnyObject {
|
||||
func connect()
|
||||
func disconnect(closeCode: UInt16)
|
||||
func write(string: String, completion: (() -> ())?)
|
||||
func write(stringData: Data, 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(closeCode: CloseCode.normal.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public enum WebSocketEvent {
|
||||
case connected([String: String])
|
||||
case disconnected(String, UInt16)
|
||||
case text(String)
|
||||
case binary(Data)
|
||||
case pong(Data?)
|
||||
case ping(Data?)
|
||||
case error(Error?)
|
||||
case viabilityChanged(Bool)
|
||||
case reconnectSuggested(Bool)
|
||||
case cancelled
|
||||
case peerClosed
|
||||
}
|
||||
|
||||
public protocol WebSocketDelegate: AnyObject {
|
||||
func didReceive(event: WebSocketEvent, client: WebSocketClient)
|
||||
}
|
||||
|
||||
open class WebSocket: WebSocketClient, EngineDelegate {
|
||||
private let engine: Engine
|
||||
public weak var delegate: WebSocketDelegate?
|
||||
public var onEvent: ((WebSocketEvent) -> Void)?
|
||||
|
||||
public var request: URLRequest
|
||||
// Where the callback is executed. It defaults to the main UI thread queue.
|
||||
public var callbackQueue = DispatchQueue.main
|
||||
public var respondToPingWithPong: Bool {
|
||||
set {
|
||||
guard let e = engine as? WSEngine else { return }
|
||||
e.respondToPingWithPong = newValue
|
||||
}
|
||||
get {
|
||||
guard let e = engine as? WSEngine else { return true }
|
||||
return e.respondToPingWithPong
|
||||
}
|
||||
}
|
||||
|
||||
public init(request: URLRequest, engine: Engine) {
|
||||
self.request = request
|
||||
self.engine = engine
|
||||
}
|
||||
|
||||
public convenience init(request: URLRequest, certPinner: CertificatePinning? = FoundationSecurity(), compressionHandler: CompressionHandler? = nil, useCustomEngine: Bool = true) {
|
||||
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *), !useCustomEngine {
|
||||
self.init(request: request, engine: NativeEngine())
|
||||
} else if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) {
|
||||
self.init(request: request, engine: WSEngine(transport: TCPTransport(), certPinner: certPinner, compressionHandler: compressionHandler))
|
||||
} else {
|
||||
self.init(request: request, engine: WSEngine(transport: FoundationTransport(), certPinner: certPinner, compressionHandler: compressionHandler))
|
||||
}
|
||||
}
|
||||
|
||||
public func connect() {
|
||||
engine.register(delegate: self)
|
||||
engine.start(request: request)
|
||||
}
|
||||
|
||||
public func disconnect(closeCode: UInt16 = CloseCode.normal.rawValue) {
|
||||
engine.stop(closeCode: closeCode)
|
||||
}
|
||||
|
||||
public func forceDisconnect() {
|
||||
engine.forceStop()
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: (() -> ())?) {
|
||||
write(data: data, opcode: .binaryFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(string: String, completion: (() -> ())?) {
|
||||
engine.write(string: string, completion: completion)
|
||||
}
|
||||
|
||||
public func write(stringData: Data, completion: (() -> ())?) {
|
||||
write(data: stringData, opcode: .textFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func write(ping: Data, completion: (() -> ())?) {
|
||||
write(data: ping, opcode: .ping, completion: completion)
|
||||
}
|
||||
|
||||
public func write(pong: Data, completion: (() -> ())?) {
|
||||
write(data: pong, opcode: .pong, completion: completion)
|
||||
}
|
||||
|
||||
private func write(data: Data, opcode: FrameOpCode, completion: (() -> ())?) {
|
||||
engine.write(data: data, opcode: opcode, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - EngineDelegate
|
||||
public func didReceive(event: WebSocketEvent) {
|
||||
callbackQueue.async { [weak self] in
|
||||
guard let s = self else { return }
|
||||
s.delegate?.didReceive(event: event, client: s)
|
||||
s.onEvent?(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FoundationTransport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum FoundationTransportError: Error {
|
||||
case invalidRequest
|
||||
case invalidOutputStream
|
||||
case timeout
|
||||
}
|
||||
|
||||
public class FoundationTransport: NSObject, Transport, StreamDelegate {
|
||||
private weak var delegate: TransportEventClient?
|
||||
private let workQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
|
||||
private var inputStream: InputStream?
|
||||
private var outputStream: OutputStream?
|
||||
private var isOpen = false
|
||||
private var onConnect: ((InputStream, OutputStream) -> Void)?
|
||||
private var isTLS = false
|
||||
private var certPinner: CertificatePinning?
|
||||
|
||||
public var usingTLS: Bool {
|
||||
return self.isTLS
|
||||
}
|
||||
|
||||
public init(streamConfiguration: ((InputStream, OutputStream) -> Void)? = nil) {
|
||||
super.init()
|
||||
onConnect = streamConfiguration
|
||||
}
|
||||
|
||||
deinit {
|
||||
inputStream?.delegate = nil
|
||||
outputStream?.delegate = nil
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
guard let parts = url.getParts() else {
|
||||
delegate?.connectionChanged(state: .failed(FoundationTransportError.invalidRequest))
|
||||
return
|
||||
}
|
||||
self.certPinner = certificatePinning
|
||||
self.isTLS = parts.isTLS
|
||||
var readStream: Unmanaged<CFReadStream>?
|
||||
var writeStream: Unmanaged<CFWriteStream>?
|
||||
let h = parts.host as NSString
|
||||
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(parts.port), &readStream, &writeStream)
|
||||
inputStream = readStream!.takeRetainedValue()
|
||||
outputStream = writeStream!.takeRetainedValue()
|
||||
guard let inStream = inputStream, let outStream = outputStream else {
|
||||
return
|
||||
}
|
||||
inStream.delegate = self
|
||||
outStream.delegate = self
|
||||
|
||||
if isTLS {
|
||||
let key = CFStreamPropertyKey(rawValue: kCFStreamPropertySocketSecurityLevel)
|
||||
CFReadStreamSetProperty(inStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
|
||||
CFWriteStreamSetProperty(outStream, key, kCFStreamSocketSecurityLevelNegotiatedSSL)
|
||||
}
|
||||
|
||||
onConnect?(inStream, outStream)
|
||||
|
||||
isOpen = false
|
||||
CFReadStreamSetDispatchQueue(inStream, workQueue)
|
||||
CFWriteStreamSetDispatchQueue(outStream, workQueue)
|
||||
inStream.open()
|
||||
outStream.open()
|
||||
|
||||
|
||||
workQueue.asyncAfter(deadline: .now() + timeout, execute: { [weak self] in
|
||||
guard let s = self else { return }
|
||||
if !s.isOpen {
|
||||
s.delegate?.connectionChanged(state: .failed(FoundationTransportError.timeout))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
if let stream = inputStream {
|
||||
stream.delegate = nil
|
||||
CFReadStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
if let stream = outputStream {
|
||||
stream.delegate = nil
|
||||
CFWriteStreamSetDispatchQueue(stream, nil)
|
||||
stream.close()
|
||||
}
|
||||
isOpen = false
|
||||
outputStream = nil
|
||||
inputStream = nil
|
||||
}
|
||||
|
||||
public func register(delegate: TransportEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> ())) {
|
||||
guard let outStream = outputStream else {
|
||||
completion(FoundationTransportError.invalidOutputStream)
|
||||
return
|
||||
}
|
||||
var total = 0
|
||||
let buffer = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
|
||||
//NOTE: this might need to be dispatched to the work queue instead of being written inline. TBD.
|
||||
while total < data.count {
|
||||
let written = outStream.write(buffer, maxLength: data.count)
|
||||
if written < 0 {
|
||||
completion(FoundationTransportError.invalidOutputStream)
|
||||
return
|
||||
}
|
||||
total += written
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
private func getSecurityData() -> (SecTrust?, String?) {
|
||||
#if os(watchOS)
|
||||
return (nil, nil)
|
||||
#else
|
||||
guard let outputStream = outputStream else {
|
||||
return (nil, nil)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
return (trust, domain)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func read() {
|
||||
guard let stream = inputStream else {
|
||||
return
|
||||
}
|
||||
let maxBuffer = 4096
|
||||
let buf = NSMutableData(capacity: maxBuffer)
|
||||
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
|
||||
let length = stream.read(buffer, maxLength: maxBuffer)
|
||||
if length < 1 {
|
||||
return
|
||||
}
|
||||
let data = Data(bytes: buffer, count: length)
|
||||
delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
|
||||
// MARK: - StreamDelegate
|
||||
|
||||
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
|
||||
switch eventCode {
|
||||
case .hasBytesAvailable:
|
||||
if aStream == inputStream {
|
||||
read()
|
||||
}
|
||||
case .errorOccurred:
|
||||
delegate?.connectionChanged(state: .failed(aStream.streamError))
|
||||
case .endEncountered:
|
||||
if aStream == inputStream {
|
||||
delegate?.connectionChanged(state: .cancelled)
|
||||
}
|
||||
case .openCompleted:
|
||||
if aStream == inputStream {
|
||||
let (trust, domain) = getSecurityData()
|
||||
if let pinner = certPinner, let trust = trust {
|
||||
pinner.evaluateTrust(trust: trust, domain: domain, completion: { [weak self] (state) in
|
||||
switch state {
|
||||
case .success:
|
||||
self?.isOpen = true
|
||||
self?.delegate?.connectionChanged(state: .connected)
|
||||
case .failed(let error):
|
||||
self?.delegate?.connectionChanged(state: .failed(error))
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
isOpen = true
|
||||
delegate?.connectionChanged(state: .connected)
|
||||
}
|
||||
}
|
||||
case .endEncountered:
|
||||
if aStream == inputStream {
|
||||
delegate?.connectionChanged(state: .cancelled)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// HTTPTransport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if canImport(Network)
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public enum TCPTransportError: Error {
|
||||
case invalidRequest
|
||||
}
|
||||
|
||||
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
||||
public class TCPTransport: Transport {
|
||||
private var connection: NWConnection?
|
||||
private let queue = DispatchQueue(label: "com.vluxe.starscream.networkstream", attributes: [])
|
||||
private weak var delegate: TransportEventClient?
|
||||
private var isRunning = false
|
||||
private var isTLS = false
|
||||
|
||||
public var usingTLS: Bool {
|
||||
return self.isTLS
|
||||
}
|
||||
|
||||
public init(connection: NWConnection) {
|
||||
self.connection = connection
|
||||
start()
|
||||
}
|
||||
|
||||
public init() {
|
||||
//normal connection, will use the "connect" method below
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
guard let parts = url.getParts() else {
|
||||
delegate?.connectionChanged(state: .failed(TCPTransportError.invalidRequest))
|
||||
return
|
||||
}
|
||||
self.isTLS = parts.isTLS
|
||||
let options = NWProtocolTCP.Options()
|
||||
options.connectionTimeout = Int(timeout.rounded(.up))
|
||||
|
||||
let tlsOptions = isTLS ? NWProtocolTLS.Options() : nil
|
||||
if let tlsOpts = tlsOptions {
|
||||
sec_protocol_options_set_verify_block(tlsOpts.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in
|
||||
let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue()
|
||||
guard let pinner = certificatePinning else {
|
||||
sec_protocol_verify_complete(true)
|
||||
return
|
||||
}
|
||||
pinner.evaluateTrust(trust: trust, domain: parts.host, completion: { (state) in
|
||||
switch state {
|
||||
case .success:
|
||||
sec_protocol_verify_complete(true)
|
||||
case .failed(_):
|
||||
sec_protocol_verify_complete(false)
|
||||
}
|
||||
})
|
||||
}, queue)
|
||||
}
|
||||
let parameters = NWParameters(tls: tlsOptions, tcp: options)
|
||||
let conn = NWConnection(host: NWEndpoint.Host.name(parts.host, nil), port: NWEndpoint.Port(rawValue: UInt16(parts.port))!, using: parameters)
|
||||
connection = conn
|
||||
start()
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
isRunning = false
|
||||
connection?.cancel()
|
||||
}
|
||||
|
||||
public func register(delegate: TransportEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> ())) {
|
||||
connection?.send(content: data, completion: .contentProcessed { (error) in
|
||||
completion(error)
|
||||
})
|
||||
}
|
||||
|
||||
private func start() {
|
||||
guard let conn = connection else {
|
||||
return
|
||||
}
|
||||
conn.stateUpdateHandler = { [weak self] (newState) in
|
||||
switch newState {
|
||||
case .ready:
|
||||
self?.delegate?.connectionChanged(state: .connected)
|
||||
case .waiting:
|
||||
self?.delegate?.connectionChanged(state: .waiting)
|
||||
case .cancelled:
|
||||
self?.delegate?.connectionChanged(state: .cancelled)
|
||||
case .failed(let error):
|
||||
self?.delegate?.connectionChanged(state: .failed(error))
|
||||
case .setup, .preparing:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
conn.viabilityUpdateHandler = { [weak self] (isViable) in
|
||||
self?.delegate?.connectionChanged(state: .viability(isViable))
|
||||
}
|
||||
|
||||
conn.betterPathUpdateHandler = { [weak self] (isBetter) in
|
||||
self?.delegate?.connectionChanged(state: .shouldReconnect(isBetter))
|
||||
}
|
||||
|
||||
conn.start(queue: queue)
|
||||
isRunning = true
|
||||
readLoop()
|
||||
}
|
||||
|
||||
//readLoop keeps reading from the connection to get the latest content
|
||||
private func readLoop() {
|
||||
if !isRunning {
|
||||
return
|
||||
}
|
||||
connection?.receive(minimumIncompleteLength: 2, maximumLength: 4096, completion: {[weak self] (data, context, isComplete, error) in
|
||||
guard let s = self else {return}
|
||||
if let data = data {
|
||||
s.delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
|
||||
// Refer to https://developer.apple.com/documentation/network/implementing_netcat_with_network_framework
|
||||
if let context = context, context.isFinal, isComplete {
|
||||
if let delegate = s.delegate {
|
||||
// Let the owner of this TCPTransport decide what to do next: disconnect or reconnect?
|
||||
delegate.connectionChanged(state: .peerClosed)
|
||||
} else {
|
||||
// No use to keep connection alive
|
||||
s.disconnect()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if error == nil {
|
||||
s.readLoop()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
#else
|
||||
typealias TCPTransport = FoundationTransport
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Transport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/23/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
public enum ConnectionState {
|
||||
/// Ready connections can send and receive data
|
||||
case connected
|
||||
|
||||
/// Waiting connections have not yet been started, or do not have a viable network
|
||||
case waiting
|
||||
|
||||
/// Cancelled connections have been invalidated by the client and will send no more events
|
||||
case cancelled
|
||||
|
||||
/// Failed connections are disconnected and can no longer send or receive data
|
||||
case failed(Error?)
|
||||
|
||||
/// Viability (connection status) of the connection has updated
|
||||
/// e.g. connection is down, connection came back up, etc.
|
||||
case viability(Bool)
|
||||
|
||||
/// Connection ca be upgraded to wifi from cellular.
|
||||
/// You should consider reconnecting to take advantage of this.
|
||||
case shouldReconnect(Bool)
|
||||
|
||||
/// Received data
|
||||
case receive(Data)
|
||||
|
||||
/// Remote peer has closed the network connection.
|
||||
case peerClosed
|
||||
}
|
||||
|
||||
public protocol TransportEventClient: AnyObject {
|
||||
func connectionChanged(state: ConnectionState)
|
||||
}
|
||||
|
||||
public protocol Transport: AnyObject {
|
||||
func register(delegate: TransportEventClient)
|
||||
func connect(url: URL, timeout: Double, certificatePinning: CertificatePinning?)
|
||||
func disconnect()
|
||||
func write(data: Data, completion: @escaping ((Error?) -> ()))
|
||||
var usingTLS: Bool { get }
|
||||
}
|
||||
+8
-7
@@ -1,15 +1,16 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Starscream"
|
||||
s.version = "2.0.0"
|
||||
s.summary = "A conforming WebSocket RFC 6455 client library in Swift for iOS and OSX."
|
||||
s.version = "4.0.4"
|
||||
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'
|
||||
s.author = {'Dalton Cherry' => 'http://daltoniam.com', 'Austin Cherry' => 'http://austincherry.me'}
|
||||
s.source = { :git => 'https://github.com/daltoniam/Starscream.git', :tag => "#{s.version}"}
|
||||
s.social_media_url = 'http://twitter.com/daltoniam'
|
||||
s.ios.deployment_target = '8.0'
|
||||
s.osx.deployment_target = '10.10'
|
||||
s.tvos.deployment_target = '9.0'
|
||||
s.source_files = 'Source/*.swift'
|
||||
s.requires_arc = 'true'
|
||||
s.ios.deployment_target = '11.0'
|
||||
s.osx.deployment_target = '10.13'
|
||||
s.tvos.deployment_target = '12.0'
|
||||
s.watchos.deployment_target = '2.0'
|
||||
s.source_files = 'Sources/**/*.swift'
|
||||
s.swift_version = '5.0'
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
@@ -14,9 +14,9 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BlueprintIdentifier = "33CCF0841F5DDC030099B092"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream iOS"
|
||||
BlueprintName = "Starscream"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -26,11 +26,33 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "33CCF0841F5DDC030099B092"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "335FA1F41F5DF71D00F6D2EC"
|
||||
BuildableName = "Starscream Tests.xctest"
|
||||
BlueprintName = "Starscream Tests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
<LocationScenarioReference
|
||||
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
|
||||
referenceType = "1">
|
||||
</LocationScenarioReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -45,14 +67,12 @@
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BlueprintIdentifier = "33CCF0841F5DDC030099B092"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream iOS"
|
||||
BlueprintName = "Starscream"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -63,9 +83,9 @@
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79E519D48B7F006071F7"
|
||||
BlueprintIdentifier = "33CCF0841F5DDC030099B092"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream iOS"
|
||||
BlueprintName = "Starscream"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E36819E48FF1009FC285"
|
||||
BuildableName = "Starscream OSXTests.xctest"
|
||||
BlueprintName = "Starscream OSXTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E36819E48FF1009FC285"
|
||||
BuildableName = "Starscream OSXTests.xctest"
|
||||
BlueprintName = "Starscream OSXTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D9C3E35E19E48FF1009FC285"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream OSX"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6B3E79F019D48B7F006071F7"
|
||||
BuildableName = "Starscream iOSTests.xctest"
|
||||
BlueprintName = "Starscream iOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,113 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0912779F1BD673A70003036D"
|
||||
BuildableName = "Starscream tvOSTests.xctest"
|
||||
BlueprintName = "Starscream tvOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0912779F1BD673A70003036D"
|
||||
BuildableName = "Starscream tvOSTests.xctest"
|
||||
BlueprintName = "Starscream tvOSTests"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "091277961BD673A70003036D"
|
||||
BuildableName = "Starscream.framework"
|
||||
BlueprintName = "Starscream tvOS"
|
||||
ReferencedContainer = "container:Starscream.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,67 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// CompressionTests.swift
|
||||
//
|
||||
// Created by Joseph Ross on 7/16/14.
|
||||
// Copyright © 2017 Joseph Ross.
|
||||
//
|
||||
// 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 XCTest
|
||||
@testable import Starscream
|
||||
|
||||
class CompressionTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// 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 testBasic() {
|
||||
let compressor = Compressor(windowBits: 15)!
|
||||
let decompressor = Decompressor(windowBits: 15)!
|
||||
|
||||
let rawData = "Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!".data(using: .utf8)!
|
||||
|
||||
let compressed = try! compressor.compress(rawData)
|
||||
let uncompressed = try! decompressor.decompress(compressed, finish: true)
|
||||
|
||||
XCTAssert(rawData == uncompressed)
|
||||
}
|
||||
|
||||
func testHugeData() {
|
||||
let compressor = Compressor(windowBits: 15)!
|
||||
let decompressor = Decompressor(windowBits: 15)!
|
||||
|
||||
// 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, rawDataLen)
|
||||
}
|
||||
|
||||
let compressed = try! compressor.compress(rawData)
|
||||
let uncompressed = try! decompressor.decompress(compressed, finish: true)
|
||||
|
||||
XCTAssert(rawData == uncompressed)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// FuzzingTests.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/28/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 XCTest
|
||||
@testable import Starscream
|
||||
|
||||
class FuzzingTests: XCTestCase {
|
||||
|
||||
var websocket: WebSocket!
|
||||
var server: MockServer!
|
||||
var uuid = ""
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let s = MockServer()
|
||||
let _ = s.start(address: "", port: 0)
|
||||
server = s
|
||||
|
||||
let transport = MockTransport(server: s)
|
||||
uuid = transport.uuid
|
||||
|
||||
let url = URL(string: "http://vluxe.io/ws")! //domain doesn't matter with the mock transport
|
||||
let request = URLRequest(url: url)
|
||||
websocket = WebSocket(request: request, engine: WSEngine(transport: transport))
|
||||
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func runWebsocket(timeout: TimeInterval = 10, serverAction: @escaping ((ServerEvent) -> Bool)) {
|
||||
let e = expectation(description: "Websocket event timeout")
|
||||
server.onEvent = { event in
|
||||
let done = serverAction(event)
|
||||
if done {
|
||||
e.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
websocket.onEvent = { event in
|
||||
switch event {
|
||||
case .text(let string):
|
||||
self.websocket.write(string: string)
|
||||
case .binary(let data):
|
||||
self.websocket.write(data: data)
|
||||
case .ping(_):
|
||||
break
|
||||
case .pong(_):
|
||||
break
|
||||
case .connected(_):
|
||||
break
|
||||
case .disconnected(let reason, let code):
|
||||
print("reason: \(reason) code: \(code)")
|
||||
case .error(_):
|
||||
break
|
||||
case .viabilityChanged(_):
|
||||
break
|
||||
case .reconnectSuggested(_):
|
||||
break
|
||||
case .cancelled:
|
||||
break
|
||||
case .peerClosed:
|
||||
break
|
||||
}
|
||||
}
|
||||
websocket.connect()
|
||||
waitForExpectations(timeout: timeout) { error in
|
||||
if let error = error {
|
||||
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(string: String, isBinary: Bool) {
|
||||
let payload = string.data(using: .utf8)!
|
||||
let code: FrameOpCode = isBinary ? .binaryFrame : .textFrame
|
||||
runWebsocket { event in
|
||||
switch event {
|
||||
case .connected(let conn, _):
|
||||
conn.write(data: payload, opcode: code)
|
||||
case .text(let conn, let text):
|
||||
if text == string && !isBinary {
|
||||
conn.write(data: Data(), opcode: .connectionClose)
|
||||
return true //success!
|
||||
} else {
|
||||
XCTFail("text does not match: source: [\(string)] response: [\(text)]")
|
||||
}
|
||||
case .binary(let conn, let data):
|
||||
if payload.count == data.count && isBinary {
|
||||
conn.write(data: Data(), opcode: .connectionClose)
|
||||
return true //success!
|
||||
} else {
|
||||
XCTFail("binary does not match: source: [\(payload.count)] response: [\(data.count)]")
|
||||
}
|
||||
case .disconnected(_, _, _):
|
||||
return false
|
||||
default:
|
||||
XCTFail("recieved unexpected server event: \(event)")
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//These are the Autobahn test cases as unit tests
|
||||
|
||||
|
||||
/// MARK : - Framing cases
|
||||
|
||||
// case 1.1.1
|
||||
func testCase1() {
|
||||
sendMessage(string: "", isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.2
|
||||
func testCase2() {
|
||||
sendMessage(string: String(repeating: "*", count: 125), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.3
|
||||
func testCase3() {
|
||||
sendMessage(string: String(repeating: "*", count: 126), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.4
|
||||
func testCase4() {
|
||||
sendMessage(string: String(repeating: "*", count: 127), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.5
|
||||
func testCase5() {
|
||||
sendMessage(string: String(repeating: "*", count: 128), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.6
|
||||
func testCase6() {
|
||||
sendMessage(string: String(repeating: "*", count: 65535), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.1.7, 1.1.8
|
||||
func testCase7() {
|
||||
sendMessage(string: String(repeating: "*", count: 65536), isBinary: false)
|
||||
}
|
||||
|
||||
// case 1.2.1
|
||||
func testCase9() {
|
||||
sendMessage(string: "", isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.2
|
||||
func testCase10() {
|
||||
sendMessage(string: String(repeating: "*", count: 125), isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.3
|
||||
func testCase11() {
|
||||
sendMessage(string: String(repeating: "*", count: 126), isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.4
|
||||
func testCase12() {
|
||||
sendMessage(string: String(repeating: "*", count: 127), isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.5
|
||||
func testCase13() {
|
||||
sendMessage(string: String(repeating: "*", count: 128), isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.6
|
||||
func testCase14() {
|
||||
sendMessage(string: String(repeating: "*", count: 65535), isBinary: true)
|
||||
}
|
||||
|
||||
// case 1.2.7, 1.2.8
|
||||
func testCase15() {
|
||||
sendMessage(string: String(repeating: "*", count: 65536), isBinary: true)
|
||||
}
|
||||
|
||||
//TODO: the rest of them.
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// MockServer.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/29/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
@testable import Starscream
|
||||
|
||||
public class MockConnection: Connection, HTTPServerDelegate, FramerEventClient, FrameCollectorDelegate {
|
||||
let transport: MockTransport
|
||||
private let httpHandler = FoundationHTTPServerHandler()
|
||||
private let framer = WSFramer(isServer: true)
|
||||
private let frameHandler = FrameCollector()
|
||||
private var didUpgrade = false
|
||||
public var onEvent: ((ConnectionEvent) -> Void)?
|
||||
fileprivate weak var delegate: ConnectionDelegate?
|
||||
|
||||
init(transport: MockTransport) {
|
||||
self.transport = transport
|
||||
httpHandler.register(delegate: self)
|
||||
framer.register(delegate: self)
|
||||
frameHandler.delegate = self
|
||||
}
|
||||
|
||||
func add(data: Data) {
|
||||
if !didUpgrade {
|
||||
httpHandler.parse(data: data)
|
||||
} else {
|
||||
framer.add(data: data)
|
||||
}
|
||||
}
|
||||
|
||||
public func write(data: Data, opcode: FrameOpCode) {
|
||||
let wsData = framer.createWriteFrame(opcode: opcode, payload: data, isCompressed: false)
|
||||
transport.received(data: wsData)
|
||||
}
|
||||
|
||||
/// MARK: - HTTPServerDelegate
|
||||
|
||||
public func didReceive(event: HTTPEvent) {
|
||||
switch event {
|
||||
case .success(let headers):
|
||||
didUpgrade = true
|
||||
//TODO: add headers and key check?
|
||||
let response = httpHandler.createResponse(headers: [:])
|
||||
transport.received(data: response)
|
||||
delegate?.didReceive(event: .connected(self, headers))
|
||||
onEvent?(.connected(headers))
|
||||
case .failure(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
/// MARK: - FrameCollectorDelegate
|
||||
|
||||
public func frameProcessed(event: FrameEvent) {
|
||||
switch event {
|
||||
case .frame(let frame):
|
||||
frameHandler.add(frame: frame)
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func didForm(event: FrameCollector.Event) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
delegate?.didReceive(event: .text(self, string))
|
||||
onEvent?(.text(string))
|
||||
case .binary(let data):
|
||||
delegate?.didReceive(event: .binary(self, data))
|
||||
onEvent?(.binary(data))
|
||||
case .pong(let data):
|
||||
delegate?.didReceive(event: .pong(self, data))
|
||||
onEvent?(.pong(data))
|
||||
case .ping(let data):
|
||||
delegate?.didReceive(event: .ping(self, data))
|
||||
onEvent?(.ping(data))
|
||||
case .closed(let reason, let code):
|
||||
delegate?.didReceive(event: .disconnected(self, reason, code))
|
||||
onEvent?(.disconnected(reason, code))
|
||||
case .error(let error):
|
||||
onEvent?(.error(error))
|
||||
}
|
||||
}
|
||||
|
||||
public func decompress(data: Data, isFinal: Bool) -> Data? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class MockServer: Server, ConnectionDelegate {
|
||||
fileprivate var connections = [String: MockConnection]()
|
||||
|
||||
public var onEvent: ((ServerEvent) -> Void)?
|
||||
|
||||
public func start(address: String, port: UInt16) -> Error? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func connect(transport: MockTransport) {
|
||||
let conn = MockConnection(transport: transport)
|
||||
conn.delegate = self
|
||||
connections[transport.uuid] = conn
|
||||
}
|
||||
|
||||
public func disconnect(uuid: String) {
|
||||
// guard let conn = connections[uuid] else {
|
||||
// return
|
||||
// }
|
||||
//TODO: force disconnect
|
||||
connections.removeValue(forKey: uuid)
|
||||
}
|
||||
|
||||
public func write(data: Data, uuid: String) {
|
||||
guard let conn = connections[uuid] else {
|
||||
return
|
||||
}
|
||||
conn.add(data: data)
|
||||
}
|
||||
|
||||
/// MARK: - MockConnectionDelegate
|
||||
public func didReceive(event: ServerEvent) {
|
||||
onEvent?(event)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// MockTransport.swift
|
||||
// Starscream
|
||||
//
|
||||
// Created by Dalton Cherry on 1/29/19.
|
||||
// Copyright © 2019 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
@testable import Starscream
|
||||
|
||||
public class MockTransport: Transport {
|
||||
|
||||
public var usingTLS: Bool {
|
||||
return false
|
||||
}
|
||||
private weak var delegate: TransportEventClient?
|
||||
|
||||
private let id: String
|
||||
weak var server: MockServer?
|
||||
var uuid: String {
|
||||
return id
|
||||
}
|
||||
|
||||
public init(server: MockServer) {
|
||||
self.server = server
|
||||
self.id = UUID().uuidString
|
||||
}
|
||||
|
||||
public func register(delegate: TransportEventClient) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double, certificatePinning: CertificatePinning?) {
|
||||
server?.connect(transport: self)
|
||||
delegate?.connectionChanged(state: .connected)
|
||||
}
|
||||
|
||||
public func disconnect() {
|
||||
server?.disconnect(uuid: uuid)
|
||||
}
|
||||
|
||||
public func write(data: Data, completion: @escaping ((Error?) -> ())) {
|
||||
server?.write(data: data, uuid: uuid)
|
||||
}
|
||||
|
||||
public func received(data: Data) {
|
||||
delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
|
||||
public func getSecurityData() -> (SecTrust?, String?) {
|
||||
return (nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
public class MockSecurity: CertificatePinning, HeaderValidator {
|
||||
|
||||
public func evaluateTrust(trust: SecTrust, domain: String?, completion: ((PinningState) -> ())) {
|
||||
completion(.success)
|
||||
}
|
||||
|
||||
public func validate(headers: [String: String], key: String) -> Error? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,24 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// StarscreamTests.swift
|
||||
// StarscreamTests
|
||||
//
|
||||
// Created by Austin Cherry on 9/25/14.
|
||||
// Copyright (c) 2014 Vluxe. All rights reserved.
|
||||
// Copyright © 2014 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 XCTest
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -12,18 +12,11 @@
|
||||
5C178E271B62D0B900A97204 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5C178E251B62D0B900A97204 /* Main.storyboard */; };
|
||||
5C178E291B62D0B900A97204 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5C178E281B62D0B900A97204 /* Images.xcassets */; };
|
||||
5C178E2C1B62D0B900A97204 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5C178E2A1B62D0B900A97204 /* LaunchScreen.xib */; };
|
||||
5C178E381B62D0B900A97204 /* AutobahnTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C178E371B62D0B900A97204 /* AutobahnTests.swift */; };
|
||||
5C178E521B62D11200A97204 /* Starscream.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C178E491B62D0EF00A97204 /* Starscream.framework */; };
|
||||
5CF1D07222552B770081C8A8 /* Starscream.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5C178E491B62D0EF00A97204 /* Starscream.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
5C178E321B62D0B900A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E141B62D0B900A97204 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 5C178E1B1B62D0B900A97204;
|
||||
remoteInfo = Autobahn;
|
||||
};
|
||||
5C178E481B62D0EF00A97204 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
@@ -31,50 +24,43 @@
|
||||
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 */ = {
|
||||
5CF1D06B22552AA30081C8A8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277971BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOS";
|
||||
remoteGlobalIDString = 335FA2021F5DF71D00F6D2EC;
|
||||
remoteInfo = "Starscream Tests";
|
||||
};
|
||||
5C42C3D71D8DF51C00947AA2 /* PBXContainerItemProxy */ = {
|
||||
5CF1D07322552B770081C8A8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277A01BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOSTests";
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CCF0841F5DDC030099B092;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
5CF1D07522552B770081C8A8 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
5CF1D07222552B770081C8A8 /* Starscream.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
5C178E1C1B62D0B900A97204 /* Autobahn.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Autobahn.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C178E201B62D0B900A97204 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -83,9 +69,6 @@
|
||||
5C178E261B62D0B900A97204 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
5C178E281B62D0B900A97204 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
5C178E2B1B62D0B900A97204 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
5C178E311B62D0B900A97204 /* AutobahnTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutobahnTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5C178E361B62D0B900A97204 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5C178E371B62D0B900A97204 /* AutobahnTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutobahnTests.swift; sourceTree = "<group>"; };
|
||||
5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Starscream.xcodeproj; path = ../../Starscream.xcodeproj; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -98,13 +81,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C178E2E1B62D0B900A97204 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -112,7 +88,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E1E1B62D0B900A97204 /* Autobahn */,
|
||||
5C178E341B62D0B900A97204 /* AutobahnTests */,
|
||||
5C178E1D1B62D0B900A97204 /* Products */,
|
||||
5C178E411B62D0EF00A97204 /* Starscream.xcodeproj */,
|
||||
);
|
||||
@@ -122,7 +97,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E1C1B62D0B900A97204 /* Autobahn.app */,
|
||||
5C178E311B62D0B900A97204 /* AutobahnTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -148,32 +122,11 @@
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5C178E341B62D0B900A97204 /* AutobahnTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E371B62D0B900A97204 /* AutobahnTests.swift */,
|
||||
5C178E351B62D0B900A97204 /* Supporting Files */,
|
||||
);
|
||||
path = AutobahnTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5C178E351B62D0B900A97204 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E361B62D0B900A97204 /* Info.plist */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5C178E421B62D0EF00A97204 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C178E491B62D0EF00A97204 /* Starscream.framework */,
|
||||
5C178E4B1B62D0EF00A97204 /* StarscreamTests.xctest */,
|
||||
5C178E4D1B62D0EF00A97204 /* Starscream.framework */,
|
||||
5C178E4F1B62D0EF00A97204 /* StarscreamOSXTests.xctest */,
|
||||
5C42C3D61D8DF51C00947AA2 /* Starscream.framework */,
|
||||
5C42C3D81D8DF51C00947AA2 /* Starscream tvOSTests.xctest */,
|
||||
5CF1D06C22552AA30081C8A8 /* Starscream Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -188,60 +141,40 @@
|
||||
5C178E181B62D0B900A97204 /* Sources */,
|
||||
5C178E191B62D0B900A97204 /* Frameworks */,
|
||||
5C178E1A1B62D0B900A97204 /* Resources */,
|
||||
5CF1D07522552B770081C8A8 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5C178E511B62D10A00A97204 /* PBXTargetDependency */,
|
||||
5CF1D07422552B770081C8A8 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Autobahn;
|
||||
productName = Autobahn;
|
||||
productReference = 5C178E1C1B62D0B900A97204 /* Autobahn.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
5C178E301B62D0B900A97204 /* AutobahnTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 5C178E3E1B62D0B900A97204 /* Build configuration list for PBXNativeTarget "AutobahnTests" */;
|
||||
buildPhases = (
|
||||
5C178E2D1B62D0B900A97204 /* Sources */,
|
||||
5C178E2E1B62D0B900A97204 /* Frameworks */,
|
||||
5C178E2F1B62D0B900A97204 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
5C178E331B62D0B900A97204 /* PBXTargetDependency */,
|
||||
);
|
||||
name = AutobahnTests;
|
||||
productName = AutobahnTests;
|
||||
productReference = 5C178E311B62D0B900A97204 /* AutobahnTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
5C178E141B62D0B900A97204 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftMigration = 0700;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0640;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = vluxe;
|
||||
TargetAttributes = {
|
||||
5C178E1B1B62D0B900A97204 = {
|
||||
CreatedOnToolsVersion = 6.4;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
5C178E301B62D0B900A97204 = {
|
||||
CreatedOnToolsVersion = 6.4;
|
||||
LastSwiftMigration = 0800;
|
||||
TestTargetID = 5C178E1B1B62D0B900A97204;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 5C178E171B62D0B900A97204 /* Build configuration list for PBXProject "Autobahn" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -259,7 +192,6 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
5C178E1B1B62D0B900A97204 /* Autobahn */,
|
||||
5C178E301B62D0B900A97204 /* AutobahnTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -272,39 +204,11 @@
|
||||
remoteRef = 5C178E481B62D0EF00A97204 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C178E4B1B62D0EF00A97204 /* StarscreamTests.xctest */ = {
|
||||
5CF1D06C22552AA30081C8A8 /* 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 = 5CF1D06B22552AA30081C8A8 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
@@ -320,13 +224,6 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C178E2F1B62D0B900A97204 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -339,27 +236,19 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
5C178E2D1B62D0B900A97204 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C178E381B62D0B900A97204 /* AutobahnTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
5C178E331B62D0B900A97204 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 5C178E1B1B62D0B900A97204 /* Autobahn */;
|
||||
targetProxy = 5C178E321B62D0B900A97204 /* PBXContainerItemProxy */;
|
||||
};
|
||||
5C178E511B62D10A00A97204 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = Starscream;
|
||||
targetProxy = 5C178E501B62D10A00A97204 /* PBXContainerItemProxy */;
|
||||
};
|
||||
5CF1D07422552B770081C8A8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = Starscream;
|
||||
targetProxy = 5CF1D07322552B770081C8A8 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@@ -386,23 +275,36 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -418,7 +320,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -431,17 +333,29 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -457,9 +371,10 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
@@ -468,58 +383,36 @@
|
||||
5C178E3C1B62D0B900A97204 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
INFOPLIST_FILE = Autobahn/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5C178E3D1B62D0B900A97204 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
|
||||
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;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
5C178E3F1B62D0B900A97204 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = AutobahnTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Autobahn.app/Autobahn";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
5C178E401B62D0B900A97204 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(SDKROOT)/Developer/Library/Frameworks",
|
||||
"$(inherited)",
|
||||
);
|
||||
INFOPLIST_FILE = AutobahnTests/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Autobahn.app/Autobahn";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@@ -544,15 +437,6 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
5C178E3E1B62D0B900A97204 /* Build configuration list for PBXNativeTarget "AutobahnTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
5C178E3F1B62D0B900A97204 /* Debug */,
|
||||
5C178E401B62D0B900A97204 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 5C178E141B62D0B900A97204 /* Project object */;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
@@ -30,6 +40,16 @@
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
@@ -59,6 +79,16 @@
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.vluxe.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -18,33 +18,39 @@ class ViewController: UIViewController {
|
||||
super.viewDidLoad()
|
||||
getCaseCount()
|
||||
//getTestInfo(1)
|
||||
//runTest(304)
|
||||
}
|
||||
|
||||
func removeSocket(_ s: WebSocket) {
|
||||
socketArray = socketArray.filter{$0 != s}
|
||||
func removeSocket(_ s: WebSocket?) {
|
||||
guard let s = s else {return}
|
||||
socketArray = socketArray.filter{$0 !== s}
|
||||
}
|
||||
|
||||
func getCaseCount() {
|
||||
|
||||
let s = WebSocket(url: URL(string: "ws://\(host)/getCaseCount")!, protocols: [])
|
||||
let req = URLRequest(url: URL(string: "ws://\(host)/getCaseCount")!)
|
||||
let s = WebSocket(request: req)
|
||||
socketArray.append(s)
|
||||
s.onText = {[unowned self] (text: String) in
|
||||
if let c = Int(text) {
|
||||
print("number of cases is: \(c)")
|
||||
self.caseCount = c
|
||||
s.onEvent = { [weak self] event in
|
||||
switch event {
|
||||
case .text(let string):
|
||||
if let c = Int(string) {
|
||||
print("number of cases is: \(c)")
|
||||
self?.caseCount = c
|
||||
}
|
||||
case .disconnected(_, _):
|
||||
self?.runTest(1)
|
||||
self?.removeSocket(s)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
self.getTestInfo(1)
|
||||
self.removeSocket(s)
|
||||
}
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func getTestInfo(_ caseNum: Int) {
|
||||
let s = createSocket("getCaseInfo",caseNum)
|
||||
socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
// s.onText = { (text: String) in
|
||||
// let data = text.dataUsingEncoding(NSUTF8StringEncoding)
|
||||
// do {
|
||||
// let resp: AnyObject? = try NSJSONSerialization.JSONObjectWithData(data!,
|
||||
@@ -60,14 +66,19 @@ class ViewController: UIViewController {
|
||||
// print("error parsing the json")
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
var once = false
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
self.runTest(caseNum)
|
||||
s.onEvent = { [weak self] event in
|
||||
switch event {
|
||||
case .disconnected(_, _), .error(_):
|
||||
if !once {
|
||||
once = true
|
||||
self?.runTest(caseNum)
|
||||
}
|
||||
self?.removeSocket(s)
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.removeSocket(s)
|
||||
}
|
||||
s.connect()
|
||||
}
|
||||
@@ -75,84 +86,99 @@ class ViewController: UIViewController {
|
||||
func runTest(_ caseNum: Int) {
|
||||
let s = createSocket("runCase",caseNum)
|
||||
self.socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
s.write(string: text)
|
||||
}
|
||||
s.onData = {(data: Data) in
|
||||
s.write(data: data)
|
||||
}
|
||||
|
||||
var once = false
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
print("case:\(caseNum) finished")
|
||||
self.verifyTest(caseNum)
|
||||
self.removeSocket(s)
|
||||
s.onEvent = { [weak self, weak s] event in
|
||||
switch event {
|
||||
case .disconnected(_, _), .error(_):
|
||||
if !once {
|
||||
once = true
|
||||
print("case:\(caseNum) finished")
|
||||
//self?.verifyTest(caseNum) //disabled since it slows down the tests
|
||||
let nextCase = caseNum+1
|
||||
if nextCase <= (self?.caseCount)! {
|
||||
self?.runTest(nextCase)
|
||||
//self?.getTestInfo(nextCase) //disabled since it slows down the tests
|
||||
} else {
|
||||
self?.finishReports()
|
||||
}
|
||||
self?.removeSocket(s)
|
||||
}
|
||||
self?.removeSocket(s)
|
||||
case .text(let string):
|
||||
s?.write(string: string)
|
||||
case .binary(let data):
|
||||
s?.write(data: data)
|
||||
// case .error(let error):
|
||||
// print("got an error: \(error)")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func verifyTest(_ caseNum: Int) {
|
||||
let s = createSocket("getCaseStatus",caseNum)
|
||||
self.socketArray.append(s)
|
||||
s.onText = {(text: String) in
|
||||
let data = text.data(using: String.Encoding.utf8)
|
||||
do {
|
||||
let resp: Any? = try JSONSerialization.jsonObject(with: data!,
|
||||
options: JSONSerialization.ReadingOptions())
|
||||
if let dict = resp as? Dictionary<String,String> {
|
||||
if let status = dict["behavior"] {
|
||||
if status == "OK" {
|
||||
print("SUCCESS: \(caseNum)")
|
||||
return
|
||||
}
|
||||
}
|
||||
print("FAILURE: \(caseNum)")
|
||||
}
|
||||
} catch {
|
||||
print("error parsing the json")
|
||||
}
|
||||
}
|
||||
var once = false
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
if !once {
|
||||
once = true
|
||||
let nextCase = caseNum+1
|
||||
if nextCase <= self.caseCount {
|
||||
self.getTestInfo(nextCase)
|
||||
} else {
|
||||
self.finishReports()
|
||||
}
|
||||
}
|
||||
self.removeSocket(s)
|
||||
}
|
||||
s.connect()
|
||||
}
|
||||
// func verifyTest(_ caseNum: Int) {
|
||||
// let s = createSocket("getCaseStatus",caseNum)
|
||||
// self.socketArray.append(s)
|
||||
// s.onText = { (text: String) in
|
||||
// let data = text.data(using: String.Encoding.utf8)
|
||||
// do {
|
||||
// let resp: Any? = try JSONSerialization.jsonObject(with: data!,
|
||||
// options: JSONSerialization.ReadingOptions())
|
||||
// if let dict = resp as? Dictionary<String,String> {
|
||||
// if let status = dict["behavior"] {
|
||||
// if status == "OK" {
|
||||
// print("SUCCESS: \(caseNum)")
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// print("FAILURE: \(caseNum)")
|
||||
// }
|
||||
// } catch {
|
||||
// print("error parsing the json")
|
||||
// }
|
||||
// }
|
||||
// var once = false
|
||||
// s.onDisconnect = { [weak self, weak s] (error: Error?) in
|
||||
// if !once {
|
||||
// once = true
|
||||
// let nextCase = caseNum+1
|
||||
// print("next test is: \(nextCase)")
|
||||
// if nextCase <= (self?.caseCount)! {
|
||||
// self?.getTestInfo(nextCase)
|
||||
// } else {
|
||||
// self?.finishReports()
|
||||
// }
|
||||
// }
|
||||
// self?.removeSocket(s)
|
||||
// }
|
||||
// s.connect()
|
||||
// }
|
||||
|
||||
func finishReports() {
|
||||
let s = createSocket("updateReports",0)
|
||||
self.socketArray.append(s)
|
||||
s.onDisconnect = {[unowned self] (error: NSError?) in
|
||||
print("finished all the tests!")
|
||||
self.removeSocket(s)
|
||||
s.onEvent = { [weak self, weak s] event in
|
||||
switch event {
|
||||
case .disconnected(_, _):
|
||||
print("finished all the tests!")
|
||||
self?.removeSocket(s)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
s.connect()
|
||||
}
|
||||
|
||||
func createSocket(_ cmd: String, _ caseNum: Int) -> WebSocket {
|
||||
return WebSocket(url: URL(string: "ws://\(host)\(buildPath(cmd,caseNum))")!, protocols: [])
|
||||
let req = URLRequest(url: URL(string: "ws://\(host)\(buildPath(cmd,caseNum))")!)
|
||||
//return WebSocket(request: req, compressionHandler: WSCompression())
|
||||
return WebSocket(request: req)
|
||||
}
|
||||
|
||||
func buildPath(_ cmd: String, _ caseNum: Int) -> String {
|
||||
return "/\(cmd)?case=\(caseNum)&agent=Starscream"
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
super.didReceiveMemoryWarning()
|
||||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// AutobahnTests.swift
|
||||
// AutobahnTests
|
||||
//
|
||||
// Created by Dalton Cherry on 7/24/15.
|
||||
// Copyright (c) 2015 vluxe. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class AutobahnTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// 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 testExample() {
|
||||
// This is an example of a functional test case.
|
||||
XCTAssert(true, "Pass")
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure() {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?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>com.vluxe.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -20,37 +20,9 @@
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6B3E79E519D48B7F006071F7;
|
||||
remoteGlobalIDString = 33CCF0841F5DDC030099B092;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
5C06AE8A1B08044600D41060 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D9C3E35F19E48FF1009FC285;
|
||||
remoteInfo = StarscreamOSX;
|
||||
};
|
||||
5C06AE8C1B08044600D41060 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = D9C3E36919E48FF1009FC285;
|
||||
remoteInfo = StarscreamOSXTests;
|
||||
};
|
||||
5C42C3E01D8F31DC00947AA2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277971BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOS";
|
||||
};
|
||||
5C42C3E21D8F31DC00947AA2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 091277A01BD673A70003036D;
|
||||
remoteInfo = "Starscream tvOSTests";
|
||||
};
|
||||
6B3E7A0D19D48D00006071F7 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
@@ -58,12 +30,12 @@
|
||||
remoteGlobalIDString = 6B3E79E619D48B7F006071F7;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
6B3E7A0F19D48D00006071F7 /* PBXContainerItemProxy */ = {
|
||||
8AF56D58224A80460020CD2F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E7A0819D48D00006071F7 /* Starscream.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 6B3E79F119D48B7F006071F7;
|
||||
remoteInfo = StarscreamTests;
|
||||
remoteGlobalIDString = 335FA2021F5DF71D00F6D2EC;
|
||||
remoteInfo = "Starscream Tests";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
@@ -143,11 +115,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6B3E7A0E19D48D00006071F7 /* Starscream.framework */,
|
||||
6B3E7A1019D48D00006071F7 /* Starscream iOSTests.xctest */,
|
||||
5C06AE8B1B08044600D41060 /* Starscream.framework */,
|
||||
5C06AE8D1B08044600D41060 /* StarscreamOSXTests.xctest */,
|
||||
5C42C3E11D8F31DC00947AA2 /* Starscream.framework */,
|
||||
5C42C3E31D8F31DC00947AA2 /* Starscream tvOSTests.xctest */,
|
||||
8AF56D59224A80460020CD2F /* Starscream Tests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -181,7 +149,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0800;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = vluxe;
|
||||
TargetAttributes = {
|
||||
5C765ADA199A6DAA003D9110 = {
|
||||
@@ -192,7 +160,7 @@
|
||||
};
|
||||
buildConfigurationList = 5C765AD6199A6DAA003D9110 /* Build configuration list for PBXProject "SimpleTest" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -215,34 +183,6 @@
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
5C06AE8B1B08044600D41060 /* Starscream.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Starscream.framework;
|
||||
remoteRef = 5C06AE8A1B08044600D41060 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C06AE8D1B08044600D41060 /* Starscream OSXTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream OSXTests.xctest";
|
||||
remoteRef = 5C06AE8C1B08044600D41060 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C42C3E11D8F31DC00947AA2 /* Starscream.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = Starscream.framework;
|
||||
remoteRef = 5C42C3E01D8F31DC00947AA2 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
5C42C3E31D8F31DC00947AA2 /* Starscream tvOSTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream tvOSTests.xctest";
|
||||
remoteRef = 5C42C3E21D8F31DC00947AA2 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
6B3E7A0E19D48D00006071F7 /* Starscream.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
@@ -250,11 +190,11 @@
|
||||
remoteRef = 6B3E7A0D19D48D00006071F7 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
6B3E7A1019D48D00006071F7 /* Starscream iOSTests.xctest */ = {
|
||||
8AF56D59224A80460020CD2F /* Starscream Tests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = "Starscream iOSTests.xctest";
|
||||
remoteRef = 6B3E7A0F19D48D00006071F7 /* PBXContainerItemProxy */;
|
||||
path = "Starscream Tests.xctest";
|
||||
remoteRef = 8AF56D58224A80460020CD2F /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
@@ -307,18 +247,27 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -354,18 +303,27 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
@@ -396,10 +354,11 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
INFOPLIST_FILE = SimpleTest/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.io.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -409,10 +368,11 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
|
||||
INFOPLIST_FILE = SimpleTest/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.io.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 3.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
BIN
Binary file not shown.
+10
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
LastUpgradeVersion = "0900"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,6 +40,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
@@ -69,6 +70,7 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
@@ -85,6 +87,13 @@
|
||||
ReferencedContainer = "container:SimpleTest.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "OS_ACTIVITY_MODE"
|
||||
value = "disable"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// SimpleTest
|
||||
//
|
||||
// Created by Dalton Cherry on 8/12/14.
|
||||
// Copyright (c) 2014 vluxe. All rights reserved.
|
||||
// Copyright © 2014 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
@@ -14,7 +28,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
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
|
||||
@@ -1,43 +1,82 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// ViewController.swift
|
||||
// SimpleTest
|
||||
//
|
||||
// Created by Dalton Cherry on 8/12/14.
|
||||
// Copyright (c) 2014 vluxe. All rights reserved.
|
||||
// Copyright © 2014 Vluxe. All rights reserved.
|
||||
//
|
||||
// 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 UIKit
|
||||
import Starscream
|
||||
|
||||
class ViewController: UIViewController, WebSocketDelegate {
|
||||
var socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat", "superchat"])
|
||||
var socket: WebSocket!
|
||||
var isConnected = false
|
||||
let server = WebSocketServer()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
//https://echo.websocket.org
|
||||
var request = URLRequest(url: URL(string: "http://localhost:8080")!) //https://localhost:8080
|
||||
request.timeoutInterval = 5
|
||||
socket = WebSocket(request: request)
|
||||
socket.delegate = self
|
||||
socket.connect()
|
||||
}
|
||||
|
||||
// MARK: Websocket Delegate Methods.
|
||||
|
||||
func websocketDidConnect(socket: WebSocket) {
|
||||
print("websocket is connected")
|
||||
}
|
||||
|
||||
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
|
||||
if let e = error {
|
||||
print("websocket is disconnected: \(e.localizedDescription)")
|
||||
} else {
|
||||
print("websocket disconnected")
|
||||
// MARK: - WebSocketDelegate
|
||||
func didReceive(event: Starscream.WebSocketEvent, client: Starscream.WebSocketClient) {
|
||||
switch event {
|
||||
case .connected(let headers):
|
||||
isConnected = true
|
||||
print("websocket is connected: \(headers)")
|
||||
case .disconnected(let reason, let code):
|
||||
isConnected = false
|
||||
print("websocket is disconnected: \(reason) with code: \(code)")
|
||||
case .text(let string):
|
||||
print("Received text: \(string)")
|
||||
case .binary(let data):
|
||||
print("Received data: \(data.count)")
|
||||
case .ping(_):
|
||||
break
|
||||
case .pong(_):
|
||||
break
|
||||
case .viabilityChanged(_):
|
||||
break
|
||||
case .reconnectSuggested(_):
|
||||
break
|
||||
case .cancelled:
|
||||
isConnected = false
|
||||
case .error(let error):
|
||||
isConnected = false
|
||||
handleError(error)
|
||||
case .peerClosed:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
|
||||
print("Received text: \(text)")
|
||||
}
|
||||
|
||||
func websocketDidReceiveData(socket: WebSocket, data: Data) {
|
||||
print("Received data: \(data.count)")
|
||||
func handleError(_ error: Error?) {
|
||||
if let e = error as? WSError {
|
||||
print("websocket encountered an error: \(e.message)")
|
||||
} else if let e = error {
|
||||
print("websocket encountered an error: \(e.localizedDescription)")
|
||||
} else {
|
||||
print("websocket encountered an error")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Write Text Action
|
||||
@@ -49,7 +88,7 @@ class ViewController: UIViewController, WebSocketDelegate {
|
||||
// MARK: Disconnect Action
|
||||
|
||||
@IBAction func disconnect(_ sender: UIBarButtonItem) {
|
||||
if socket.isConnected {
|
||||
if isConnected {
|
||||
sender.title = "Connect"
|
||||
socket.disconnect()
|
||||
} else {
|
||||
|
||||
@@ -19,7 +19,7 @@ EM.run {
|
||||
|
||||
ws.onmessage { |msg|
|
||||
puts "message from client: #{msg}"
|
||||
ws.send Faker::Hacker.say_something_smart
|
||||
ws.send +Faker::Hacker.say_something_smart
|
||||
}
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: 96cd79a6b7ef6a2ff2d00638c73bd195a5322586
|
||||
|
||||
PODFILE CHECKSUM: 96d91933fe13671aaa81af8a8675ff7698068845
|
||||
|
||||
COCOAPODS: 1.6.0.beta.1
|
||||
@@ -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: 96cd79a6b7ef6a2ff2d00638c73bd195a5322586
|
||||
|
||||
PODFILE CHECKSUM: 96d91933fe13671aaa81af8a8675ff7698068845
|
||||
|
||||
COCOAPODS: 1.6.0.beta.1
|
||||
+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
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?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">
|
||||
<string></string>
|
||||
</plist>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_Pods_WebSocketsOrgEcho : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_Pods_WebSocketsOrgEcho
|
||||
@end
|
||||
Generated
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
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 && exit ${PIPESTATUS[0]})
|
||||
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" || exit 1
|
||||
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
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Starscream/Starscream.framework/Headers"
|
||||
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
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Starscream/Starscream.framework/Headers"
|
||||
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,42 @@
|
||||
//
|
||||
// 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 {
|
||||
|
||||
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,29 @@
|
||||
default_platform(:ios)
|
||||
|
||||
update_fastlane
|
||||
|
||||
|
||||
desc "Run tests"
|
||||
lane :test do
|
||||
run_tests(
|
||||
devices: ["iPhone 14 Pro", "iPad Pro (11-inch) (4th generation)"],
|
||||
)
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
desc "Deploy new version"
|
||||
lane :release do
|
||||
version = version_bump_podspec(path: "Starscream.podspec", version_number: ENV["TAG"])
|
||||
changelog = changelog_from_git_commits(merge_commit_filtering: "exclude_merges")
|
||||
|
||||
github_release = set_github_release(
|
||||
repository_name: "daltoniam/starscream",
|
||||
api_token: ENV["GITHUB_TOKEN"],
|
||||
name: version,
|
||||
tag_name: version,
|
||||
description: changelog,
|
||||
commitish: "master"
|
||||
)
|
||||
pod_push(allow_warnings: false, verbose: true)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
### test
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane test
|
||||
```
|
||||
|
||||
Run tests
|
||||
|
||||
----
|
||||
|
||||
|
||||
## iOS
|
||||
|
||||
### ios release
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios release
|
||||
```
|
||||
|
||||
Deploy 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).
|
||||
Reference in New Issue
Block a user