refactor and swift4

This commit is contained in:
daltoniam 2017-09-25 16:00:28 -05:00
parent 143ab16c1a
commit f506be54ae
11 changed files with 487 additions and 320 deletions

View File

@ -2,6 +2,24 @@
All notable changes to this project will be documented in this file.
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
#### [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 add 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.

View File

@ -1,6 +1,6 @@
![starscream](https://raw.githubusercontent.com/daltoniam/starscream/assets/starscream.jpg)
Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6455)) client library in Swift for iOS and OSX.
Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6455)) client library in Swift.
Its Objective-C counterpart can be found here: [Jetfire](https://github.com/acmacalister/jetfire)
@ -35,7 +35,7 @@ After you are connected, there are some delegate methods that we need to impleme
websocketDidConnect is called as soon as the client connects to the server.
```swift
func websocketDidConnect(socket: WebSocket) {
func websocketDidConnect(socket: WebSocketClient) {
print("websocket is connected")
}
```
@ -45,7 +45,7 @@ func websocketDidConnect(socket: WebSocket) {
websocketDidDisconnect is called as soon as the client is disconnected from the server.
```swift
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
print("websocket is disconnected: \(error?.localizedDescription)")
}
```
@ -55,7 +55,7 @@ func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
websocketDidReceiveMessage is called when the client gets a text frame from the connection.
```swift
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
print("got some text: \(text)")
}
```
@ -65,7 +65,7 @@ func websocketDidReceiveMessage(socket: WebSocket, text: String) {
websocketDidReceiveData is called when the client gets a binary frame from the connection.
```swift
func websocketDidReceiveData(socket: WebSocket, data: Data) {
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
print("got some data: \(data.count)")
}
```
@ -75,7 +75,7 @@ func websocketDidReceiveData(socket: WebSocket, data: Data) {
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?) {
func websocketDidReceivePong(socket: WebSocketClient, data: Data?) {
print("Got pong! Maybe some data: \(data?.count)")
}
```
@ -89,7 +89,7 @@ socket.onConnect = {
print("websocket is connected")
}
//websocketDidDisconnect
socket.onDisconnect = { (error: NSError?) in
socket.onDisconnect = { (error: Error?) in
print("websocket is disconnected: \(error?.localizedDescription)")
}
//websocketDidReceiveMessage
@ -104,7 +104,7 @@ socket.onData = { (data: Data) in
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`.
One more: you can listen to socket connection and disconnection via notifications. Starscream posts `WebsocketDidConnectNotification` and `WebsocketDidDisconnectNotification`. You can find an `Error` 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?
@ -156,9 +156,12 @@ if socket.isConnected {
You can also override the default websocket headers with your own custom ones like so:
```swift
socket.headers["Sec-WebSocket-Protocol"] = "someother protocols"
socket.headers["Sec-WebSocket-Version"] = "14"
socket.headers["My-Awesome-Header"] = "Everything is Awesome!"
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
request.timeoutInterval = 5
request.setValue("someother protocols", forHTTPHeaderField: "Sec-WebSocket-Protocol")
request.setValue("14", forHTTPHeaderField: "Sec-WebSocket-Version")
request.setValue("Everything is Awesome!", forHTTPHeaderField: "My-Awesome-Header")
let socket = WebSocket(request: request)
```
### Custom HTTP Method
@ -166,15 +169,11 @@ socket.headers["My-Awesome-Header"] = "Everything is Awesome!"
Your server may use a different HTTP method when connecting to the websocket:
```swift
socket.httpMethod = .post
var request = URLRequest(url: URL(string: "ws://localhost:8080/")!)
request.httpMethod = "POST"
request.timeoutInterval = 5
let socket = WebSocket(request: request)
```
you can use a custom string:
```swift
socket.httpMethod = .custom(value: "mycustomhttpmethod")
```
### Protocols
@ -262,7 +261,7 @@ To use Starscream in your project add the following 'Podfile' to your project
platform :ios, '9.0'
use_frameworks!
pod 'Starscream', '~> 2.0.3'
pod 'Starscream', '~> 3.0.0'
Then run:
@ -284,7 +283,7 @@ $ brew install carthage
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
```
github "daltoniam/Starscream" >= 2.0.3
github "daltoniam/Starscream" >= 3.0.0
```
### Rogue
@ -294,7 +293,7 @@ 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.
@ -307,7 +306,7 @@ Once you have your Swift package set up, adding Starscream as a dependency is as
```swift
dependencies: [
.Package(url: "https://github.com/daltoniam/Starscream.git", majorVersion: 2)
.Package(url: "https://github.com/daltoniam/Starscream.git", majorVersion: 3)
]
```
@ -333,7 +332,7 @@ In most cases you do not need the extra info and should use the normal delegate.
#### websocketDidReceiveMessage
```swift
func websocketDidReceiveMessage(socket: WebSocket, text: String, response: WebSocket.WSResponse {
func websocketDidReceiveMessage(socket: WebSocketClient, text: String, response: WebSocket.WSResponse {
print("got some text: \(text)")
print("First frame for this message arrived on \(response.firstFrame)")
}
@ -341,7 +340,7 @@ func websocketDidReceiveMessage(socket: WebSocket, text: String, response: WebSo
#### websocketDidReceiveData
```swift
func websocketDidReceiveData(socket: WebSocket, data: Date, response: WebSocket.WSResponse) {
func websocketDidReceiveData(socket: WebSocketClient, data: Date, response: WebSocket.WSResponse) {
print("got some data it long: \(data.count)")
print("A total of \(response.frameCount) frames were used to send this data")
}
@ -350,22 +349,20 @@ func websocketDidReceiveData(socket: WebSocket, data: Date, response: WebSocket.
#### websocketHttpUpgrade
These methods are called when the HTTP upgrade request is sent and when the response returns.
```swift
func websocketHttpUpgrade(socket: WebSocket, request: CFHTTPMessage) {
func websocketHttpUpgrade(socket: WebSocketClient, request: CFHTTPMessage) {
print("the http request was sent we can check the raw http if we need to")
}
```
```swift
func websocketHttpUpgrade(socket: WebSocket, response: CFHTTPMessage) {
func websocketHttpUpgrade(socket: WebSocketClient, response: CFHTTPMessage) {
print("the http response has returned.")
}
```
## TODOs
- [ ] WatchOS?
- [ ] Linux Support?
- [ ] Add Unit Tests - Local Swift websocket server
- [ ] Add Unit Tests - Local WebSocket server that runs against Autobahn
## License

View File

@ -92,7 +92,7 @@ class Decompressor {
guard (res == Z_OK && strm.avail_out > 0)
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
else {
throw NSError(domain: WebSocket.ErrorDomain, code: Int(WebSocket.InternalErrorCode.compressionError.rawValue), userInfo: nil)
throw NSError(domain: WebSocket.ErrorDomain, code: Int(InternalErrorCode.compressionError.rawValue), userInfo: nil)
}
}
@ -157,7 +157,7 @@ class Compressor {
guard res == Z_OK && strm.avail_out > 0
|| (res == Z_BUF_ERROR && Int(strm.avail_out) == buffer.count)
else {
throw NSError(domain: WebSocket.ErrorDomain, code: Int(WebSocket.InternalErrorCode.compressionError.rawValue), userInfo: nil)
throw NSError(domain: WebSocket.ErrorDomain, code: Int(InternalErrorCode.compressionError.rawValue), userInfo: nil)
}
compressed.removeLast(4)

View File

@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.1</string>
<string>3.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -19,7 +19,8 @@
// limitations under the License.
//
//////////////////////////////////////////////////////////////////////////////////////////////////
#if os(Linux)
#else
import Foundation
import Security
@ -258,3 +259,4 @@ open class SSLSecurity : SSLTrustValidator {
}
#endif

View File

@ -3,7 +3,7 @@
// Websocket.swift
//
// Created by Dalton Cherry on 7/16/14.
// Copyright (c) 2014-2016 Dalton Cherry.
// Copyright (c) 2014-2017 Dalton Cherry.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -27,28 +27,258 @@ 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)
//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
}
//Error codes
enum InternalErrorCode: UInt16 {
// 0-999 WebSocket status codes not used
case outputStreamWriteError = 1
case compressionError = 2
case invalidSSLError = 3
case writeTimeoutError = 4
}
//WebSocketClient is setup to be dependency injection for testing
public protocol WebSocketClient: class {
var delegate: WebSocketDelegate? {get set }
var disableSSLCertValidation: Bool { get set }
#if os(Linux)
#else
var security: SSLTrustValidator? { get set }
var enabledSSLCipherSuites: [SSLCipherSuite]? { get set }
#endif
var isConnected: Bool { get }
func connect()
func disconnect(forceTimeout: TimeInterval?, closeCode: UInt16)
func write(string: String, completion: (() -> ())?)
func write(data: Data, completion: (() -> ())?)
func write(ping: Data, completion: (() -> ())?)
}
//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 disconnect() {
disconnect(forceTimeout: nil, closeCode: CloseCode.normal.rawValue)
}
}
//SSL settings for the stream
public struct SSLSettings {
let useSSL: Bool
let disableCertValidation: Bool
#if os(Linux)
#else
let cipherSuites: [SSLCipherSuite]?
#endif
}
public protocol WSStreamDelegate: class {
func newBytesInStream()
func streamDidError(error: Error?)
}
//This protocol is to allow custom implemention of the underlining stream. This way custom socket libraries (e.g. linux) can be used
public protocol WSStream {
weak var delegate: WSStreamDelegate? {get set}
func connect(url: URL, port: Int, timeout: TimeInterval, ssl: SSLSettings, completion: @escaping ((Error?) -> Void))
func write(data: Data) -> Int
func read() -> Data?
func cleanup()
#if os(Linux) || os(watchOS)
#else
func sslTrust() -> (trust: SecTrust?, domain: String?)
#endif
}
open class FoundationStream : NSObject, WSStream, StreamDelegate {
private static let sharedWorkQueue = DispatchQueue(label: "com.vluxe.starscream.websocket", attributes: [])
private var inputStream: InputStream?
private var outputStream: OutputStream?
public weak var delegate: WSStreamDelegate?
let BUFFER_MAX = 4096
public func connect(url: URL, port: Int, timeout: TimeInterval, ssl: SSLSettings, completion: @escaping ((Error?) -> Void)) {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let h = url.host! as NSString
CFStreamCreatePairWithSocketToHost(nil, h, UInt32(port), &readStream, &writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
guard let inStream = inputStream, let outStream = outputStream else { return }
inStream.delegate = self
outStream.delegate = self
if ssl.useSSL {
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
if ssl.disableCertValidation {
#if os(watchOS) //watchOS us unfortunately is missing the kCFStream properties to make this work
#else
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)
#endif
}
#if os(Linux)
#else
if let cipherSuites = ssl.cipherSuites {
#if os(watchOS) //watchOS us unfortunately is missing the kCFStream properties to make this work
#else
if let sslContextIn = CFReadStreamCopyProperty(inputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext?,
let sslContextOut = CFWriteStreamCopyProperty(outputStream, CFStreamPropertyKey(rawValue: kCFStreamPropertySSLContext)) as! SSLContext? {
let resIn = SSLSetEnabledCiphers(sslContextIn, cipherSuites, cipherSuites.count)
let resOut = SSLSetEnabledCiphers(sslContextOut, cipherSuites, cipherSuites.count)
if resIn != errSecSuccess {
completion(errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn)))
}
if resOut != errSecSuccess {
completion(errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut)))
}
}
#endif
}
#endif
}
CFReadStreamSetDispatchQueue(inStream, FoundationStream.sharedWorkQueue)
CFWriteStreamSetDispatchQueue(outStream, FoundationStream.sharedWorkQueue)
inStream.open()
outStream.open()
var out = timeout// wait X seconds before giving up
FoundationStream.sharedWorkQueue.async { [weak self] in
while !outStream.hasSpaceAvailable {
usleep(100) // wait until the socket is ready
out -= 100
if out < 0 {
guard let s = self else {return}
let errCode = InternalErrorCode.writeTimeoutError.rawValue
completion(s.errorWithDetail("write wait timed out", code: errCode))
return
} else if let error = outStream.streamError {
completion(error)
return // disconnectStream will be called.
}
}
completion(nil) //success!
}
}
public func write(data: Data) -> Int {
guard let outStream = outputStream else {return 0}
let buffer = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
return outStream.write(buffer, maxLength: data.count)
}
public func read() -> Data? {
let buf = NSMutableData(capacity: BUFFER_MAX)
let buffer = UnsafeMutableRawPointer(mutating: buf!.bytes).assumingMemoryBound(to: UInt8.self)
let length = inputStream!.read(buffer, maxLength: BUFFER_MAX)
if length < 1 {
return nil
}
return Data(bytes: buffer, count: length)
}
public func cleanup() {
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
}
#if os(Linux) || os(watchOS)
#else
public func sslTrust() -> (trust: SecTrust?, domain: String?) {
let trust = outputStream!.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust?
let domain = outputStream!.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
return (trust, domain)
}
#endif
/**
Delegate for the stream methods. Processes incoming bytes
*/
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode == .hasBytesAvailable {
if aStream == inputStream {
delegate?.newBytesInStream()
}
} else if eventCode == .errorOccurred {
delegate?.streamDidError(error: aStream.streamError)
} else if eventCode == .endEncountered {
delegate?.streamDidError(error: nil)
}
}
private func errorWithDetail(_ detail: String, code: UInt16) -> Error {
var details = [String: String]()
details[NSLocalizedDescriptionKey] = detail
return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) as Error
}
}
//WebSocket implementation
//standard delegate you should use
public protocol WebSocketDelegate: class {
func websocketDidConnect(socket: WebSocketClient)
func websocketDidDisconnect(socket: WebSocketClient, error: Error?)
func websocketDidReceiveMessage(socket: WebSocketClient, text: String)
func websocketDidReceiveData(socket: WebSocketClient, data: Data)
}
//got pongs
public protocol WebSocketPongDelegate: class {
func websocketDidReceivePong(socket: WebSocket, data: Data?)
func websocketDidReceivePong(socket: WebSocketClient, data: Data?)
}
// A Delegate with more advanced info on messages and connection etc.
public protocol WebSocketAdvancedDelegate: class {
func websocketDidConnect(socket: WebSocket)
func websocketDidDisconnect(socket: WebSocket, error: NSError?)
func websocketDidDisconnect(socket: WebSocket, error: Error?)
func websocketDidReceiveMessage(socket: WebSocket, text: String, response: WebSocket.WSResponse)
func websocketDidReceiveData(socket: WebSocket, data: Data, response: WebSocket.WSResponse)
func websocketHttpUpgrade(socket: WebSocket, request: CFHTTPMessage)
func websocketHttpUpgrade(socket: WebSocket, response: CFHTTPMessage)
func websocketHttpUpgrade(socket: WebSocket, request: String)
func websocketHttpUpgrade(socket: WebSocket, response: String)
}
open class WebSocket : NSObject, StreamDelegate {
open class WebSocket : NSObject, StreamDelegate, WebSocketClient, WSStreamDelegate {
public enum OpCode : UInt8 {
case continueFrame = 0x0
@ -61,34 +291,11 @@ open class WebSocket : NSObject, StreamDelegate {
// 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
case compressionError = 2
case invalidSSLError = 3
case writeTimeoutError = 4
}
// 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"
@ -130,52 +337,27 @@ open class WebSocket : NSObject, StreamDelegate {
/// Responds to callback about new messages coming in over the WebSocket
/// and also connection/disconnect messages.
public weak var delegate: WebSocketDelegate?
/// The optional advanced delegate can be used insteadof of the delegate
/// The optional advanced delegate can be used instead of of the delegate
public weak var advancedDelegate: WebSocketAdvancedDelegate?
/// Receives a callback for each pong message recived.
public weak var pongDelegate: WebSocketPongDelegate?
// MARK: - Block based API.
public enum HTTPMethod {
case get
case post
case put
case connect
case custom(value: String)
var representation: String {
switch self {
case .get:
return "GET"
case .post:
return "POST"
case .put:
return "PUT"
case .connect:
return "CONNECT"
case .custom(let value):
return value.capitalized
}
}
}
public var onConnect: (() -> Void)?
public var onDisconnect: ((NSError?) -> Void)?
public var onDisconnect: ((Error?) -> Void)?
public var onText: ((String) -> Void)?
public var onData: ((Data) -> Void)?
public var onPong: ((Data?) -> Void)?
public var httpMethod: HTTPMethod = .get
public var headers = [String: String]()
public var disableSSLCertValidation = false
public var enableCompression = true
#if os(Linux)
#else
public var security: SSLTrustValidator?
public var enabledSSLCipherSuites: [SSLCipherSuite]?
public var origin: String?
public var timeout = 5
#endif
public var isConnected: Bool {
connectedMutex.lock()
let isConnected = connected
@ -183,7 +365,7 @@ open class WebSocket : NSObject, StreamDelegate {
return isConnected
}
public var currentURL: URL { return url }
public var currentURL: URL { return request.url! }
// MARK: - Private
@ -198,9 +380,8 @@ open class WebSocket : NSObject, StreamDelegate {
var compressor:Compressor? = nil
}
private var url: URL
private var inputStream: InputStream?
private var outputStream: OutputStream?
private var request: URLRequest
private var stream: WSStream
private var connected = false
private var isConnecting = false
private let connectedMutex = NSLock()
@ -214,27 +395,36 @@ open class WebSocket : NSObject, StreamDelegate {
private var readyToWrite = false
private var headerSecKey = ""
private let readyToWriteMutex = NSLock()
private let notificationCenter = NotificationCenter.default
private var canDispatch: Bool {
readyToWriteMutex.lock()
let canWork = readyToWrite
readyToWriteMutex.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
if let hostUrl = URL (string: "/", relativeTo: url) {
var origin = hostUrl.absoluteString
origin.remove(at: origin.index(before: origin.endIndex))
self.origin = origin
public init(request: URLRequest, protocols: [String]? = nil, stream: WSStream = FoundationStream()) {
self.request = request
self.stream = stream
if request.value(forHTTPHeaderField: headerOriginName) == nil {
guard let url = request.url else {return}
var origin = url.absoluteString
if let hostUrl = URL (string: "/", relativeTo: url) {
origin = hostUrl.absoluteString
origin.remove(at: origin.index(before: origin.endIndex))
}
self.request.setValue(origin, forHTTPHeaderField: headerOriginName)
}
if let protocols = protocols {
self.request.setValue(protocols.joined(separator: ","), forHTTPHeaderField: headerWSProtocolName)
}
writeQueue.maxConcurrentOperationCount = 1
optionalProtocols = protocols
}
public convenience init(url: URL, protocols: [String]? = nil) {
var request = URLRequest(url: url)
request.timeoutInterval = 5
self.init(request: request, protocols: protocols)
}
// Used for specifically setting the QOS for the write queue.
@ -269,17 +459,13 @@ open class WebSocket : NSObject, StreamDelegate {
case .some(let seconds) where seconds > 0:
let milliseconds = Int(seconds * 1_000)
callbackQueue.asyncAfter(deadline: .now() + .milliseconds(milliseconds)) { [weak self] in
WebSocket.sharedWorkQueue.async {
self?.disconnectStream(nil)
}
self?.disconnectStream(nil)
}
fallthrough
case .none:
writeError(closeCode)
default:
WebSocket.sharedWorkQueue.async { [weak self] in
self?.disconnectStream(nil)
}
disconnectStream(nil)
break
}
}
@ -323,9 +509,7 @@ open class WebSocket : NSObject, StreamDelegate {
Private method that starts the connection.
*/
private func createHTTPRequest() {
let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, httpMethod.representation as CFString,
url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
guard let url = request.url else {return}
var port = url.port
if port == nil {
if supportedSSLSchemes.contains(url.scheme!) {
@ -334,37 +518,33 @@ open class WebSocket : NSObject, StreamDelegate {
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: ","))
}
request.setValue(headerWSUpgradeValue, forHTTPHeaderField: headerWSUpgradeName)
request.setValue(headerWSConnectionValue, forHTTPHeaderField: headerWSConnectionName)
headerSecKey = generateWebSocketKey()
addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue)
addHeader(urlRequest, key: headerWSKeyName, val: headerSecKey)
if let origin = origin {
addHeader(urlRequest, key: headerOriginName, val: origin)
}
request.setValue(headerWSVersionValue, forHTTPHeaderField: headerWSVersionName)
request.setValue(headerSecKey, forHTTPHeaderField: headerWSKeyName)
if enableCompression {
let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
addHeader(urlRequest, key: headerWSExtensionName, val: val)
request.setValue(val, forHTTPHeaderField: headerWSExtensionName)
}
addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)")
for (key, value) in headers {
addHeader(urlRequest, key: key, val: value)
request.setValue("\(url.host!):\(port!)", forHTTPHeaderField: headerWSHostName)
var path = url.path
if path.isEmpty {
path = "/"
}
if let cfHTTPMessage = CFHTTPMessageCopySerializedMessage(urlRequest) {
let serializedRequest = cfHTTPMessage.takeRetainedValue()
initStreamsWithData(serializedRequest as Data, Int(port!))
self.advancedDelegate?.websocketHttpUpgrade(socket: self, request: urlRequest)
if let query = url.query {
path += "?" + query
}
}
/**
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)
var httpBody = "\(request.httpMethod ?? "GET") \(path) HTTP/1.1\r\n"
if let headers = request.allHTTPHeaderFields {
for (key, val) in headers {
httpBody += "\(key): \(val)\r\n"
}
}
httpBody += "\r\n"
initStreamsWithData(httpBody.data(using: .utf8)!, Int(port!))
advancedDelegate?.websocketHttpUpgrade(socket: self, request: httpBody)
}
/**
@ -386,126 +566,81 @@ open class WebSocket : NSObject, StreamDelegate {
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)
guard let url = request.url else {
disconnectStream(nil, runDelegate: true)
return
}
// Disconnect and clean up any existing streams before setting up a new pair
disconnectStream(nil, runDelegate: false)
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!) {
certValidated = false
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
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)
let useSSL = supportedSSLSchemes.contains(url.scheme!)
#if os(Linux)
let settings = SSLSettings(useSSL: useSSL,
disableCertValidation: disableSSLCertValidation)
#else
let settings = SSLSettings(useSSL: useSSL,
disableCertValidation: disableSSLCertValidation, cipherSuites: self.enabledSSLCipherSuites)
#endif
certValidated = !useSSL
let timeout = request.timeoutInterval * 1_000_000
stream.delegate = self
stream.connect(url: url, port: port, timeout: timeout, ssl: settings, completion: { [weak self] (error) in
guard let s = self else {return}
if error != nil {
//do disconnect
return
}
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 {
WebSocket.sharedWorkQueue.async { [weak self] in
let error = self?.errorWithDetail("Error setting ingoing cypher suites", code: UInt16(resIn))
self?.disconnectStream(error)
let operation = BlockOperation()
operation.addExecutionBlock { [weak self, weak operation] in
guard let sOperation = operation, let s = self else { return }
guard !sOperation.isCancelled else { return }
// Do the pinning now if needed
#if os(Linux) || os(watchOS)
s.certValidated = false
#else
if let sec = s.security, !s.certValidated {
let trustObj = s.stream.sslTrust()
if let possibleTrust = trustObj.trust {
s.certValidated = sec.isValid(possibleTrust, domain: trustObj.domain)
} else {
s.certValidated = false
}
return
}
if resOut != errSecSuccess {
WebSocket.sharedWorkQueue.async { [weak self] in
let error = self?.errorWithDetail("Error setting outgoing cypher suites", code: UInt16(resOut))
self?.disconnectStream(error)
if !s.certValidated {
let errCode = InternalErrorCode.invalidSSLError.rawValue
let error = s.errorWithDetail("Invalid SSL certificate", code: errCode)
s.disconnectStream(error)
return
}
return
}
}
#endif
let _ = s.stream.write(data: data)
}
} else {
certValidated = true //not a https session, so no need to check SSL pinning
}
CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue)
CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue)
inStream.open()
outStream.open()
s.writeQueue.addOperation(operation)
})
self.readyToWriteMutex.lock()
self.readyToWrite = true
self.readyToWriteMutex.unlock()
let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
var out = timeout * 1_000_000 // wait 5 seconds before giving up
let operation = BlockOperation()
operation.addExecutionBlock { [weak self, weak operation] in
guard let sOperation = operation else { return }
while !outStream.hasSpaceAvailable && !sOperation.isCancelled {
usleep(100) // wait until the socket is ready
guard !sOperation.isCancelled else { return }
out -= 100
if out < 0 {
WebSocket.sharedWorkQueue.async {
self?.cleanupStream()
}
let errCode = InternalErrorCode.writeTimeoutError.rawValue
self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: errCode))
return
} else if outStream.streamError != nil {
return // disconnectStream will be called.
}
}
guard !sOperation.isCancelled, let s = self else { return }
// Do the pinning now if needed
if let sec = s.security, !s.certValidated {
if let possibleTrust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) {
let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
s.certValidated = sec.isValid(possibleTrust as! SecTrust, domain: domain)
} else {
s.certValidated = false
}
if !s.certValidated {
WebSocket.sharedWorkQueue.async {
let errCode = InternalErrorCode.invalidSSLError.rawValue
let error = s.errorWithDetail("Invalid SSL certificate", code: errCode)
s.disconnectStream(error)
}
return
}
}
outStream.write(bytes, maxLength: data.count)
}
writeQueue.addOperation(operation)
}
/**
Delegate for the stream methods. Processes incoming bytes
*/
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode == .hasBytesAvailable {
if aStream == inputStream {
processInputStream()
}
} else if eventCode == .errorOccurred {
disconnectStream(aStream.streamError as NSError?)
} else if eventCode == .endEncountered {
disconnectStream(nil)
}
public func newBytesInStream() {
processInputStream()
}
public func streamDidError(error: Error?) {
disconnectStream(error)
}
/**
Disconnect the stream object and notifies the delegate.
*/
private func disconnectStream(_ error: NSError?, runDelegate: Bool = true) {
private func disconnectStream(_ error: Error?, runDelegate: Bool = true) {
if error == nil {
writeQueue.waitUntilAllOperationsAreFinished()
} else {
@ -524,18 +659,7 @@ open class WebSocket : NSObject, StreamDelegate {
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
stream.cleanup()
fragBuffer = nil
}
@ -543,15 +667,13 @@ open class WebSocket : NSObject, StreamDelegate {
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 }
let data = stream.read()
guard let d = data else { return }
var process = false
if inputQueue.count == 0 {
process = true
}
inputQueue.append(Data(bytes: buffer, count: length))
inputQueue.append(d)
if process {
dequeueInput()
}
@ -609,7 +731,7 @@ open class WebSocket : NSObject, StreamDelegate {
for i in 0..<bufferLen {
if buffer[i] == CRLFBytes[k] {
k += 1
if k == 3 {
if k == 4 {
totalSize = i + 1
break
}
@ -633,10 +755,10 @@ open class WebSocket : NSObject, StreamDelegate {
s.onConnect?()
s.delegate?.websocketDidConnect(socket: s)
s.advancedDelegate?.websocketDidConnect(socket: s)
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
NotificationCenter.default.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
}
}
totalSize += 1 //skip the last \n
//totalSize += 1 //skip the last \n
let restSize = bufferLen - totalSize
if restSize > 0 {
processRawMessagesInBuffer(buffer + totalSize, bufferLen: restSize)
@ -650,29 +772,45 @@ open class WebSocket : NSObject, StreamDelegate {
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)
self.advancedDelegate?.websocketHttpUpgrade(socket: self, response: response)
guard let str = String(data: Data(bytes: buffer, count: bufferLen), encoding: .utf8) else { return -1 }
let splitArr = str.components(separatedBy: "\r\n")
var code = -1
var i = 0
var headers = [String: String]()
for str in splitArr {
if i == 0 {
let responseSplit = str.components(separatedBy: .whitespaces)
guard responseSplit.count > 1 else { return -1 }
if let c = Int(responseSplit[1]) {
code = c
}
} else {
let responseSplit = str.components(separatedBy: ":")
guard responseSplit.count > 1 else { break }
let key = responseSplit[0].trimmingCharacters(in: .whitespaces)
let val = responseSplit[1].trimmingCharacters(in: .whitespaces)
headers[key] = val
}
i += 1
}
advancedDelegate?.websocketHttpUpgrade(socket: self, response: str)
if code != httpSwitchProtocolCode {
return code
}
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
let headers = cfHeaders.takeRetainedValue() as NSDictionary
if let extensionHeader = headers[headerWSExtensionName as NSString] as? String {
processExtensionHeader(extensionHeader)
}
if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString {
if acceptKey.length > 0 {
if headerSecKey.characters.count > 0 {
let sha = "\(headerSecKey)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64()
if sha != acceptKey as String {
return -1
}
if let extensionHeader = headers[headerWSExtensionName] {
processExtensionHeader(extensionHeader)
}
if let acceptKey = headers[headerWSAcceptName] {
if acceptKey.characters.count > 0 {
if headerSecKey.characters.count > 0 {
let sha = "\(headerSecKey)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64()
if sha != acceptKey as String {
return -1
}
return 0
}
return 0
}
}
return -1
@ -802,7 +940,7 @@ open class WebSocket : NSObject, StreamDelegate {
closeCode = CloseCode.protocolError.rawValue
} else if payloadLen > 1 {
closeCode = WebSocket.readUint16(baseAddress, offset: offset)
if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1011 && closeCode < 3000) {
if closeCode < 1000 || (closeCode > 1003 && closeCode < 1007) || (closeCode > 1013 && closeCode < 3000) {
closeCode = CloseCode.protocolError.rawValue
}
}
@ -949,17 +1087,16 @@ open class WebSocket : NSObject, StreamDelegate {
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 {
guard let str = String(data: response.buffer! as Data, encoding: .utf8) else {
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)
s.advancedDelegate?.websocketDidReceiveMessage(socket: s, text: str! as String, response: response)
s.onText?(str)
s.delegate?.websocketDidReceiveMessage(socket: s, text: str)
s.advancedDelegate?.websocketDidReceiveMessage(socket: s, text: str, response: response)
}
}
} else if response.code == .binaryFrame {
@ -982,10 +1119,10 @@ open class WebSocket : NSObject, StreamDelegate {
/**
Create an error
*/
private func errorWithDetail(_ detail: String, code: UInt16) -> NSError {
private func errorWithDetail(_ detail: String, code: UInt16) -> Error {
var details = [String: String]()
details[NSLocalizedDescriptionKey] = detail
return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details)
return NSError(domain: WebSocket.ErrorDomain, code: Int(code), userInfo: details) as Error
}
/**
@ -1047,18 +1184,14 @@ open class WebSocket : NSObject, StreamDelegate {
}
var total = 0
while !sOperation.isCancelled {
guard let outStream = s.outputStream else { break }
let stream = s.stream
let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self)
let len = outStream.write(writeBuffer, maxLength: offset-total)
let len = stream.write(data: Data(bytes: writeBuffer, count: 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?)
s.doDisconnect(error)
break
} else {
total += len
@ -1080,7 +1213,7 @@ open class WebSocket : NSObject, StreamDelegate {
/**
Used to preform the disconnect delegate
*/
private func doDisconnect(_ error: NSError?) {
private func doDisconnect(_ error: Error?) {
guard !didDisconnect else { return }
didDisconnect = true
isConnecting = false
@ -1094,7 +1227,7 @@ open class WebSocket : NSObject, StreamDelegate {
s.delegate?.websocketDidDisconnect(socket: s, error: error)
s.advancedDelegate?.websocketDidDisconnect(socket: s, error: error)
let userInfo = error.map{ [WebsocketDisconnectionErrorKeyName: $0] }
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo)
NotificationCenter.default.post(name: NSNotification.Name(WebsocketDidDisconnectNotification), object: self, userInfo: userInfo)
}
}

View File

@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "Starscream"
s.version = "2.1.1"
s.summary = "A conforming WebSocket RFC 6455 client library in Swift for iOS and OSX."
s.version = "3.0.0"
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'}
@ -10,8 +10,8 @@ Pod::Spec.new do |s|
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.10'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'
s.source_files = 'Source/*.swift'
s.requires_arc = 'true'
s.libraries = 'z'
s.pod_target_xcconfig = {
'SWIFT_INCLUDE_PATHS' => '$(PODS_ROOT)/Starscream/zlib'

View File

@ -28,6 +28,7 @@
5C1360001C473BEF00AA3A01 /* Starscream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Starscream.h; path = Sources/Starscream.h; sourceTree = SOURCE_ROOT; };
5C1360011C473BEF00AA3A01 /* WebSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = WebSocket.swift; path = Sources/WebSocket.swift; sourceTree = SOURCE_ROOT; };
5C13600C1C473BFE00AA3A01 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = SOURCE_ROOT; };
5CAAB5D01F7987D800F3C556 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS4.0.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
6B3E7A0019D48C2F006071F7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
742419BB1DC6BDBA003ACE43 /* StarscreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StarscreamTests.swift; path = StarscreamTests/StarscreamTests.swift; sourceTree = "<group>"; };
D85927D61ED761A0003460CB /* module.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
@ -121,6 +122,7 @@
D88EAF801ED4DFD3004FE2C3 /* Frameworks */ = {
isa = PBXGroup;
children = (
5CAAB5D01F7987D800F3C556 /* libz.tbd */,
D88EAF901ED4E949004FE2C3 /* libz.tbd */,
D88EAF8D1ED4E92E004FE2C3 /* libz.tbd */,
D88EAF811ED4DFD3004FE2C3 /* libz.tbd */,
@ -327,11 +329,13 @@
PROVISIONING_PROFILE = "";
SDKROOT = "";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos watchos watchsimulator";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
TVOS_DEPLOYMENT_TARGET = 10.0;
VALID_ARCHS = "i386 x86_64 armv7s";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
};
name = Debug;
};
@ -357,11 +361,13 @@
PROVISIONING_PROFILE = "";
SDKROOT = "";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvsimulator appletvos watchos watchsimulator";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
SWIFT_VERSION = 4.0;
TVOS_DEPLOYMENT_TARGET = 10.0;
VALID_ARCHS = "i386 x86_64 armv7s";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
};
name = Release;
};
@ -414,11 +420,13 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SDKROOT = "";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
SWIFT_INCLUDE_PATHS = $SRCROOT/zlib;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VALID_ARCHS = "x86_64 i386";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
@ -465,11 +473,13 @@
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SDKROOT = "";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
SWIFT_INCLUDE_PATHS = $SRCROOT/zlib;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
VALIDATE_PRODUCT = YES;
VALID_ARCHS = "x86_64 i386";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};

View File

@ -34,7 +34,7 @@ class ViewController: UIViewController {
self?.caseCount = c
}
}
s.onDisconnect = { [weak self, weak s] (error: NSError?) in
s.onDisconnect = { [weak self, weak s] (error: Error?) in
self?.getTestInfo(1)
self?.removeSocket(s)
}
@ -62,7 +62,7 @@ class ViewController: UIViewController {
}
var once = false
s.onDisconnect = { [weak self, weak s] (error: NSError?) in
s.onDisconnect = { [weak self, weak s] (error: Error?) in
if !once {
once = true
self?.runTest(caseNum)
@ -82,11 +82,17 @@ class ViewController: UIViewController {
s?.write(data: data)
}
var once = false
s.onDisconnect = {[weak self, weak s] (error: NSError?) in
s.onDisconnect = {[weak self, weak s] (error: Error?) in
if !once {
once = true
print("case:\(caseNum) finished")
self?.verifyTest(caseNum)
//self?.verifyTest(caseNum) disabled since it slows down the tests
let nextCase = caseNum+1
if nextCase <= (self?.caseCount)! {
self?.getTestInfo(nextCase)
} else {
self?.finishReports()
}
self?.removeSocket(s)
}
}
@ -115,10 +121,11 @@ class ViewController: UIViewController {
}
}
var once = false
s.onDisconnect = { [weak self, weak s] (error: NSError?) in
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 {
@ -133,7 +140,7 @@ class ViewController: UIViewController {
func finishReports() {
let s = createSocket("updateReports",0)
self.socketArray.append(s)
s.onDisconnect = { [weak self, weak s] (error: NSError?) in
s.onDisconnect = { [weak self, weak s] (error: Error?) in
print("finished all the tests!")
self?.removeSocket(s)
}

View File

@ -20,11 +20,11 @@ class ViewController: UIViewController, WebSocketDelegate {
// MARK: Websocket Delegate Methods.
func websocketDidConnect(socket: WebSocket) {
func websocketDidConnect(socket: WebSocketClient) {
print("websocket is connected")
}
func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
func websocketDidDisconnect(socket: WebSocketClient, error: Error?) {
if let e = error {
print("websocket is disconnected: \(e.localizedDescription)")
} else {
@ -32,11 +32,11 @@ class ViewController: UIViewController, WebSocketDelegate {
}
}
func websocketDidReceiveMessage(socket: WebSocket, text: String) {
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
print("Received text: \(text)")
}
func websocketDidReceiveData(socket: WebSocket, data: Data) {
func websocketDidReceiveData(socket: WebSocketClient, data: Data) {
print("Received data: \(data.count)")
}