websocket and user center
This commit is contained in:
@@ -0,0 +1,688 @@
|
||||
//
|
||||
// SocketEngine.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 3/3/15.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import Dispatch
|
||||
import Foundation
|
||||
import Starscream
|
||||
|
||||
/// The class that handles the engine.io protocol and transports.
|
||||
/// See `SocketEnginePollable` and `SocketEngineWebsocket` for transport specific methods.
|
||||
open class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket, ConfigSettable {
|
||||
// MARK: Properties
|
||||
|
||||
private static let logType = "SocketEngine"
|
||||
|
||||
/// The queue that all engine actions take place on.
|
||||
public let engineQueue = DispatchQueue(label: "com.socketio.engineHandleQueue")
|
||||
|
||||
/// The connect parameters sent during a connect.
|
||||
public var connectParams: [String: Any]? {
|
||||
didSet {
|
||||
(urlPolling, urlWebSocket) = createURLs()
|
||||
}
|
||||
}
|
||||
|
||||
/// A dictionary of extra http headers that will be set during connection.
|
||||
public var extraHeaders: [String: String]?
|
||||
|
||||
/// A queue of engine.io messages waiting for POSTing
|
||||
///
|
||||
/// **You should not touch this directly**
|
||||
public var postWait = [String]()
|
||||
|
||||
/// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to
|
||||
/// disconnect us.
|
||||
///
|
||||
/// **Do not touch this directly**
|
||||
public var waitingForPoll = false
|
||||
|
||||
/// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to
|
||||
/// disconnect us.
|
||||
///
|
||||
/// **Do not touch this directly**
|
||||
public var waitingForPost = false
|
||||
|
||||
/// `true` if this engine is closed.
|
||||
public private(set) var closed = false
|
||||
|
||||
/// If `true` the engine will attempt to use WebSocket compression.
|
||||
public private(set) var compress = false
|
||||
|
||||
/// `true` if this engine is connected. Connected means that the initial poll connect has succeeded.
|
||||
public private(set) var connected = false
|
||||
|
||||
/// An array of HTTPCookies that are sent during the connection.
|
||||
public private(set) var cookies: [HTTPCookie]?
|
||||
|
||||
/// When `true`, the engine is in the process of switching to WebSockets.
|
||||
///
|
||||
/// **Do not touch this directly**
|
||||
public private(set) var fastUpgrade = false
|
||||
|
||||
/// When `true`, the engine will only use HTTP long-polling as a transport.
|
||||
public private(set) var forcePolling = false
|
||||
|
||||
/// When `true`, the engine will only use WebSockets as a transport.
|
||||
public private(set) var forceWebsockets = false
|
||||
|
||||
/// `true` If engine's session has been invalidated.
|
||||
public private(set) var invalidated = false
|
||||
|
||||
/// If `true`, the engine is currently in HTTP long-polling mode.
|
||||
public private(set) var polling = true
|
||||
|
||||
/// If `true`, the engine is currently seeing whether it can upgrade to WebSockets.
|
||||
public private(set) var probing = false
|
||||
|
||||
/// The URLSession that will be used for polling.
|
||||
public private(set) var session: URLSession?
|
||||
|
||||
/// The session id for this engine.
|
||||
public private(set) var sid = ""
|
||||
|
||||
/// The path to engine.io.
|
||||
public private(set) var socketPath = "/engine.io/"
|
||||
|
||||
/// The url for polling.
|
||||
public private(set) var urlPolling = URL(string: "http://localhost/")!
|
||||
|
||||
/// The url for WebSockets.
|
||||
public private(set) var urlWebSocket = URL(string: "http://localhost/")!
|
||||
|
||||
/// If `true`, then the engine is currently in WebSockets mode.
|
||||
@available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets")
|
||||
public private(set) var websocket = false
|
||||
|
||||
/// The WebSocket for this engine.
|
||||
public private(set) var ws: WebSocket?
|
||||
|
||||
/// The client for this engine.
|
||||
public weak var client: SocketEngineClient?
|
||||
|
||||
private weak var sessionDelegate: URLSessionDelegate?
|
||||
|
||||
private let url: URL
|
||||
|
||||
private var pingInterval: Int?
|
||||
private var pingTimeout = 0 {
|
||||
didSet {
|
||||
pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25000))
|
||||
}
|
||||
}
|
||||
|
||||
private var pongsMissed = 0
|
||||
private var pongsMissedMax = 0
|
||||
private var probeWait = ProbeWaitQueue()
|
||||
private var secure = false
|
||||
private var security: SocketIO.SSLSecurity?
|
||||
private var selfSigned = false
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a new engine.
|
||||
///
|
||||
/// - parameter client: The client for this engine.
|
||||
/// - parameter url: The url for this engine.
|
||||
/// - parameter config: An array of configuration options for this engine.
|
||||
public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) {
|
||||
self.client = client
|
||||
self.url = url
|
||||
|
||||
super.init()
|
||||
|
||||
setConfigs(config)
|
||||
|
||||
sessionDelegate = sessionDelegate ?? self
|
||||
|
||||
(urlPolling, urlWebSocket) = createURLs()
|
||||
}
|
||||
|
||||
/// Creates a new engine.
|
||||
///
|
||||
/// - parameter client: The client for this engine.
|
||||
/// - parameter url: The url for this engine.
|
||||
/// - parameter options: The options for this engine.
|
||||
public required convenience init(client: SocketEngineClient, url: URL, options: [String: Any]?) {
|
||||
self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? [])
|
||||
}
|
||||
|
||||
deinit {
|
||||
DefaultSocketLogger.Logger.log("Engine is being released", type: SocketEngine.logType)
|
||||
closed = true
|
||||
stopPolling()
|
||||
}
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
private func checkAndHandleEngineError(_ msg: String) {
|
||||
do {
|
||||
let dict = try msg.toDictionary()
|
||||
guard let error = dict["message"] as? String else { return }
|
||||
|
||||
/*
|
||||
0: Unknown transport
|
||||
1: Unknown sid
|
||||
2: Bad handshake request
|
||||
3: Bad request
|
||||
*/
|
||||
didError(reason: error)
|
||||
} catch {
|
||||
client?.engineDidError(reason: "Got unknown error from server \(msg)")
|
||||
}
|
||||
}
|
||||
|
||||
private func handleBase64(message: String) {
|
||||
// binary in base64 string
|
||||
let noPrefix = String(message[message.index(message.startIndex, offsetBy: 2)..<message.endIndex])
|
||||
|
||||
if let data = Data(base64Encoded: noPrefix, options: .ignoreUnknownCharacters) {
|
||||
client?.parseEngineBinaryData(data)
|
||||
}
|
||||
}
|
||||
|
||||
private func closeOutEngine(reason: String) {
|
||||
sid = ""
|
||||
closed = true
|
||||
invalidated = true
|
||||
connected = false
|
||||
|
||||
ws?.disconnect()
|
||||
stopPolling()
|
||||
client?.engineDidClose(reason: reason)
|
||||
}
|
||||
|
||||
/// Starts the connection to the server.
|
||||
open func connect() {
|
||||
engineQueue.async {
|
||||
self._connect()
|
||||
}
|
||||
}
|
||||
|
||||
private func _connect() {
|
||||
if connected {
|
||||
DefaultSocketLogger.Logger.error("Engine tried opening while connected. Assuming this was a reconnect",
|
||||
type: SocketEngine.logType)
|
||||
disconnect(reason: "reconnect")
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Starting engine. Server: \(url)", type: SocketEngine.logType)
|
||||
DefaultSocketLogger.Logger.log("Handshaking", type: SocketEngine.logType)
|
||||
|
||||
resetEngine()
|
||||
|
||||
if forceWebsockets {
|
||||
polling = false
|
||||
createWebSocketAndConnect()
|
||||
return
|
||||
}
|
||||
|
||||
var reqPolling = URLRequest(url: urlPolling, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
|
||||
|
||||
addHeaders(to: &reqPolling)
|
||||
doLongPoll(for: reqPolling)
|
||||
}
|
||||
|
||||
private func createURLs() -> (URL, URL) {
|
||||
if client == nil {
|
||||
return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!)
|
||||
}
|
||||
|
||||
var urlPolling = URLComponents(string: url.absoluteString)!
|
||||
var urlWebSocket = URLComponents(string: url.absoluteString)!
|
||||
var queryString = ""
|
||||
|
||||
urlWebSocket.path = socketPath
|
||||
urlPolling.path = socketPath
|
||||
|
||||
if secure {
|
||||
urlPolling.scheme = "https"
|
||||
urlWebSocket.scheme = "wss"
|
||||
} else {
|
||||
urlPolling.scheme = "http"
|
||||
urlWebSocket.scheme = "ws"
|
||||
}
|
||||
|
||||
if let connectParams = self.connectParams {
|
||||
for (key, value) in connectParams {
|
||||
let keyEsc = key.urlEncode()!
|
||||
let valueEsc = "\(value)".urlEncode()!
|
||||
|
||||
queryString += "&\(keyEsc)=\(valueEsc)"
|
||||
}
|
||||
}
|
||||
|
||||
urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString
|
||||
urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString
|
||||
|
||||
return (urlPolling.url!, urlWebSocket.url!)
|
||||
}
|
||||
|
||||
private func createWebSocketAndConnect() {
|
||||
var req = URLRequest(url: urlWebSocketWithSid)
|
||||
|
||||
addHeaders(to: &req, includingCookies: session?.configuration.httpCookieStorage?.cookies)
|
||||
|
||||
ws = WebSocket(request: req)
|
||||
ws?.callbackQueue = engineQueue
|
||||
ws?.enableCompression = compress
|
||||
ws?.disableSSLCertValidation = selfSigned
|
||||
ws?.security = security?.security
|
||||
|
||||
ws?.onConnect = {[weak self] in
|
||||
guard let this = self else { return }
|
||||
|
||||
this.websocketDidConnect()
|
||||
}
|
||||
|
||||
ws?.onDisconnect = {[weak self] error in
|
||||
guard let this = self else { return }
|
||||
|
||||
this.websocketDidDisconnect(error: error)
|
||||
}
|
||||
|
||||
ws?.onData = {[weak self] data in
|
||||
guard let this = self else { return }
|
||||
|
||||
this.parseEngineData(data)
|
||||
}
|
||||
|
||||
ws?.onText = {[weak self] message in
|
||||
guard let this = self else { return }
|
||||
|
||||
this.parseEngineMessage(message)
|
||||
}
|
||||
|
||||
ws?.connect()
|
||||
}
|
||||
|
||||
/// Called when an error happens during execution. Causes a disconnection.
|
||||
open func didError(reason: String) {
|
||||
DefaultSocketLogger.Logger.error("\(reason)", type: SocketEngine.logType)
|
||||
client?.engineDidError(reason: reason)
|
||||
disconnect(reason: reason)
|
||||
}
|
||||
|
||||
/// Disconnects from the server.
|
||||
///
|
||||
/// - parameter reason: The reason for the disconnection. This is communicated up to the client.
|
||||
open func disconnect(reason: String) {
|
||||
engineQueue.async {
|
||||
self._disconnect(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
private func _disconnect(reason: String) {
|
||||
guard connected && !closed else { return closeOutEngine(reason: reason) }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Engine is being closed.", type: SocketEngine.logType)
|
||||
|
||||
if polling {
|
||||
disconnectPolling(reason: reason)
|
||||
} else {
|
||||
sendWebSocketMessage("", withType: .close, withData: [])
|
||||
closeOutEngine(reason: reason)
|
||||
}
|
||||
}
|
||||
|
||||
// We need to take special care when we're polling that we send it ASAP
|
||||
// Also make sure we're on the emitQueue since we're touching postWait
|
||||
private func disconnectPolling(reason: String) {
|
||||
postWait.append(String(SocketEnginePacketType.close.rawValue))
|
||||
|
||||
doRequest(for: createRequestForPostWithPostWait()) {_, _, _ in }
|
||||
closeOutEngine(reason: reason)
|
||||
}
|
||||
|
||||
/// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in
|
||||
/// WebSocket mode.
|
||||
///
|
||||
/// **You shouldn't call this directly**
|
||||
open func doFastUpgrade() {
|
||||
if waitingForPoll {
|
||||
DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," +
|
||||
"we'll probably disconnect soon. You should report this.", type: SocketEngine.logType)
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Switching to WebSockets", type: SocketEngine.logType)
|
||||
|
||||
sendWebSocketMessage("", withType: .upgrade, withData: [])
|
||||
polling = false
|
||||
fastUpgrade = false
|
||||
probing = false
|
||||
flushProbeWait()
|
||||
|
||||
// Need to flush postWait to socket since it connected successfully
|
||||
// moved from flushProbeWait() since it is also called on connected failure, and we don't want to try and send
|
||||
// packets through WebSockets when WebSockets has failed!
|
||||
if !postWait.isEmpty {
|
||||
flushWaitingForPostToWebSocket()
|
||||
}
|
||||
}
|
||||
|
||||
private func flushProbeWait() {
|
||||
DefaultSocketLogger.Logger.log("Flushing probe wait", type: SocketEngine.logType)
|
||||
|
||||
for waiter in probeWait {
|
||||
write(waiter.msg, withType: waiter.type, withData: waiter.data)
|
||||
}
|
||||
|
||||
probeWait.removeAll(keepingCapacity: false)
|
||||
}
|
||||
|
||||
/// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when
|
||||
/// the engine is attempting to upgrade to WebSocket it does not do any POSTing.
|
||||
///
|
||||
/// **You shouldn't call this directly**
|
||||
open func flushWaitingForPostToWebSocket() {
|
||||
guard let ws = self.ws else { return }
|
||||
|
||||
for msg in postWait {
|
||||
ws.write(string: msg)
|
||||
}
|
||||
|
||||
postWait.removeAll(keepingCapacity: false)
|
||||
}
|
||||
|
||||
private func handleClose(_ reason: String) {
|
||||
client?.engineDidClose(reason: reason)
|
||||
}
|
||||
|
||||
private func handleMessage(_ message: String) {
|
||||
client?.parseEngineMessage(message)
|
||||
}
|
||||
|
||||
private func handleNOOP() {
|
||||
doPoll()
|
||||
}
|
||||
|
||||
private func handleOpen(openData: String) {
|
||||
guard let json = try? openData.toDictionary() else {
|
||||
didError(reason: "Error parsing open packet")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard let sid = json["sid"] as? String else {
|
||||
didError(reason: "Open packet contained no sid")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let upgradeWs: Bool
|
||||
|
||||
self.sid = sid
|
||||
connected = true
|
||||
pongsMissed = 0
|
||||
|
||||
if let upgrades = json["upgrades"] as? [String] {
|
||||
upgradeWs = upgrades.contains("websocket")
|
||||
} else {
|
||||
upgradeWs = false
|
||||
}
|
||||
|
||||
if let pingInterval = json["pingInterval"] as? Int, let pingTimeout = json["pingTimeout"] as? Int {
|
||||
self.pingInterval = pingInterval
|
||||
self.pingTimeout = pingTimeout
|
||||
}
|
||||
|
||||
if !forcePolling && !forceWebsockets && upgradeWs {
|
||||
createWebSocketAndConnect()
|
||||
}
|
||||
|
||||
sendPing()
|
||||
|
||||
if !forceWebsockets {
|
||||
doPoll()
|
||||
}
|
||||
|
||||
client?.engineDidOpen(reason: "Connect")
|
||||
}
|
||||
|
||||
private func handlePong(with message: String) {
|
||||
pongsMissed = 0
|
||||
|
||||
// We should upgrade
|
||||
if message == "3probe" {
|
||||
DefaultSocketLogger.Logger.log("Received probe response, should upgrade to WebSockets",
|
||||
type: SocketEngine.logType)
|
||||
|
||||
upgradeTransport()
|
||||
}
|
||||
|
||||
client?.engineDidReceivePong()
|
||||
}
|
||||
|
||||
/// Parses raw binary received from engine.io.
|
||||
///
|
||||
/// - parameter data: The data to parse.
|
||||
open func parseEngineData(_ data: Data) {
|
||||
DefaultSocketLogger.Logger.log("Got binary data: \(data)", type: SocketEngine.logType)
|
||||
|
||||
client?.parseEngineBinaryData(data.subdata(in: 1..<data.endIndex))
|
||||
}
|
||||
|
||||
/// Parses a raw engine.io packet.
|
||||
///
|
||||
/// - parameter message: The message to parse.
|
||||
open func parseEngineMessage(_ message: String) {
|
||||
DefaultSocketLogger.Logger.log("Got message: \(message)", type: SocketEngine.logType)
|
||||
|
||||
let reader = SocketStringReader(message: message)
|
||||
|
||||
if message.hasPrefix("b4") {
|
||||
return handleBase64(message: message)
|
||||
}
|
||||
|
||||
guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else {
|
||||
checkAndHandleEngineError(message)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .message:
|
||||
handleMessage(String(message.dropFirst()))
|
||||
case .noop:
|
||||
handleNOOP()
|
||||
case .pong:
|
||||
handlePong(with: message)
|
||||
case .open:
|
||||
handleOpen(openData: String(message.dropFirst()))
|
||||
case .close:
|
||||
handleClose(message)
|
||||
default:
|
||||
DefaultSocketLogger.Logger.log("Got unknown packet type", type: SocketEngine.logType)
|
||||
}
|
||||
}
|
||||
|
||||
// Puts the engine back in its default state
|
||||
private func resetEngine() {
|
||||
let queue = OperationQueue()
|
||||
queue.underlyingQueue = engineQueue
|
||||
|
||||
closed = false
|
||||
connected = false
|
||||
fastUpgrade = false
|
||||
polling = true
|
||||
probing = false
|
||||
invalidated = false
|
||||
session = Foundation.URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: queue)
|
||||
sid = ""
|
||||
waitingForPoll = false
|
||||
waitingForPost = false
|
||||
}
|
||||
|
||||
private func sendPing() {
|
||||
guard connected, let pingInterval = pingInterval else { return }
|
||||
|
||||
// Server is not responding
|
||||
if pongsMissed > pongsMissedMax {
|
||||
client?.engineDidClose(reason: "Ping timeout")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
pongsMissed += 1
|
||||
write("", withType: .ping, withData: [])
|
||||
|
||||
engineQueue.asyncAfter(deadline: DispatchTime.now() + .milliseconds(pingInterval)) {[weak self, id = self.sid] in
|
||||
// Make sure not to ping old connections
|
||||
guard let this = self, this.sid == id else { return }
|
||||
|
||||
this.sendPing()
|
||||
}
|
||||
|
||||
client?.engineDidSendPing()
|
||||
}
|
||||
|
||||
/// Called when the engine should set/update its configs from a given configuration.
|
||||
///
|
||||
/// parameter config: The `SocketIOClientConfiguration` that should be used to set/update configs.
|
||||
open func setConfigs(_ config: SocketIOClientConfiguration) {
|
||||
for option in config {
|
||||
switch option {
|
||||
case let .connectParams(params):
|
||||
connectParams = params
|
||||
case let .cookies(cookies):
|
||||
self.cookies = cookies
|
||||
case let .extraHeaders(headers):
|
||||
extraHeaders = headers
|
||||
case let .sessionDelegate(delegate):
|
||||
sessionDelegate = delegate
|
||||
case let .forcePolling(force):
|
||||
forcePolling = force
|
||||
case let .forceWebsockets(force):
|
||||
forceWebsockets = force
|
||||
case let .path(path):
|
||||
socketPath = path
|
||||
|
||||
if !socketPath.hasSuffix("/") {
|
||||
socketPath += "/"
|
||||
}
|
||||
case let .secure(secure):
|
||||
self.secure = secure
|
||||
case let .selfSigned(selfSigned):
|
||||
self.selfSigned = selfSigned
|
||||
case let .security(security):
|
||||
self.security = security
|
||||
case .compress:
|
||||
self.compress = true
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moves from long-polling to websockets
|
||||
private func upgradeTransport() {
|
||||
if ws?.isConnected ?? false {
|
||||
DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: SocketEngine.logType)
|
||||
|
||||
fastUpgrade = true
|
||||
sendPollMessage("", withType: .noop, withData: [])
|
||||
// After this point, we should not send anymore polling messages
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a message to engine.io, independent of transport.
|
||||
///
|
||||
/// - parameter msg: The message to send.
|
||||
/// - parameter type: The type of this message.
|
||||
/// - parameter data: Any data that this message has.
|
||||
open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data]) {
|
||||
engineQueue.async {
|
||||
guard self.connected else { return }
|
||||
guard !self.probing else {
|
||||
self.probeWait.append((msg, type, data))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if self.polling {
|
||||
DefaultSocketLogger.Logger.log("Writing poll: \(msg) has data: \(data.count != 0)",
|
||||
type: SocketEngine.logType)
|
||||
self.sendPollMessage(msg, withType: type, withData: data)
|
||||
} else {
|
||||
DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)",
|
||||
type: SocketEngine.logType)
|
||||
self.sendWebSocketMessage(msg, withType: type, withData: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket Methods
|
||||
|
||||
private func websocketDidConnect() {
|
||||
if !forceWebsockets {
|
||||
probing = true
|
||||
probeWebSocket()
|
||||
} else {
|
||||
connected = true
|
||||
probing = false
|
||||
polling = false
|
||||
}
|
||||
}
|
||||
|
||||
private func websocketDidDisconnect(error: Error?) {
|
||||
probing = false
|
||||
|
||||
if closed {
|
||||
client?.engineDidClose(reason: "Disconnect")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard !polling else {
|
||||
flushProbeWait()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
connected = false
|
||||
polling = true
|
||||
|
||||
if let reason = error?.localizedDescription {
|
||||
didError(reason: reason)
|
||||
} else {
|
||||
client?.engineDidClose(reason: "Socket Disconnected")
|
||||
}
|
||||
}
|
||||
|
||||
// Test Properties
|
||||
|
||||
func setConnected(_ value: Bool) {
|
||||
connected = value
|
||||
}
|
||||
}
|
||||
|
||||
extension SocketEngine {
|
||||
// MARK: URLSessionDelegate methods
|
||||
|
||||
/// Delegate called when the session becomes invalid.
|
||||
public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) {
|
||||
DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine")
|
||||
|
||||
didError(reason: "Engine URLSession became invalid")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// SocketEngineClient.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 3/19/15.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Declares that a type will be a delegate to an engine.
|
||||
@objc public protocol SocketEngineClient {
|
||||
// MARK: Methods
|
||||
|
||||
/// Called when the engine errors.
|
||||
///
|
||||
/// - parameter reason: The reason the engine errored.
|
||||
func engineDidError(reason: String)
|
||||
|
||||
/// Called when the engine closes.
|
||||
///
|
||||
/// - parameter reason: The reason that the engine closed.
|
||||
func engineDidClose(reason: String)
|
||||
|
||||
/// Called when the engine opens.
|
||||
///
|
||||
/// - parameter reason: The reason the engine opened.
|
||||
func engineDidOpen(reason: String)
|
||||
|
||||
/// Called when the engine receives a pong message.
|
||||
func engineDidReceivePong()
|
||||
|
||||
/// Called when the engine sends a ping to the server.
|
||||
func engineDidSendPing()
|
||||
|
||||
/// Called when the engine has a message that must be parsed.
|
||||
///
|
||||
/// - parameter msg: The message that needs parsing.
|
||||
func parseEngineMessage(_ msg: String)
|
||||
|
||||
/// Called when the engine receives binary data.
|
||||
///
|
||||
/// - parameter data: The data the engine received.
|
||||
func parseEngineBinaryData(_ data: Data)
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// SocketEnginePacketType.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 10/7/15.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Represents the type of engine.io packet types.
|
||||
@objc public enum SocketEnginePacketType : Int {
|
||||
/// Open message.
|
||||
case open
|
||||
|
||||
/// Close message.
|
||||
case close
|
||||
|
||||
/// Ping message.
|
||||
case ping
|
||||
|
||||
/// Pong message.
|
||||
case pong
|
||||
|
||||
/// Regular message.
|
||||
case message
|
||||
|
||||
/// Upgrade message.
|
||||
case upgrade
|
||||
|
||||
/// NOOP.
|
||||
case noop
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// SocketEnginePollable.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 1/15/16.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Protocol that is used to implement socket.io polling support
|
||||
public protocol SocketEnginePollable : SocketEngineSpec {
|
||||
// MARK: Properties
|
||||
|
||||
/// `true` If engine's session has been invalidated.
|
||||
var invalidated: Bool { get }
|
||||
|
||||
/// A queue of engine.io messages waiting for POSTing
|
||||
///
|
||||
/// **You should not touch this directly**
|
||||
var postWait: [String] { get set }
|
||||
|
||||
/// The URLSession that will be used for polling.
|
||||
var session: URLSession? { get }
|
||||
|
||||
/// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to
|
||||
/// disconnect us.
|
||||
///
|
||||
/// **Do not touch this directly**
|
||||
var waitingForPoll: Bool { get set }
|
||||
|
||||
/// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to
|
||||
/// disconnect us.
|
||||
///
|
||||
/// **Do not touch this directly**
|
||||
var waitingForPost: Bool { get set }
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Call to send a long-polling request.
|
||||
///
|
||||
/// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request.
|
||||
func doPoll()
|
||||
|
||||
/// Sends an engine.io message through the polling transport.
|
||||
///
|
||||
/// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
|
||||
///
|
||||
/// - parameter message: The message to send.
|
||||
/// - parameter withType: The type of message to send.
|
||||
/// - parameter withData: The data associated with this message.
|
||||
func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data])
|
||||
|
||||
/// Call to stop polling and invalidate the URLSession.
|
||||
func stopPolling()
|
||||
}
|
||||
|
||||
// Default polling methods
|
||||
extension SocketEnginePollable {
|
||||
func createRequestForPostWithPostWait() -> URLRequest {
|
||||
defer { postWait.removeAll(keepingCapacity: true) }
|
||||
|
||||
var postStr = ""
|
||||
|
||||
for packet in postWait {
|
||||
postStr += "\(packet.utf16.count):\(packet)"
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Created POST string: \(postStr)", type: "SocketEnginePolling")
|
||||
|
||||
var req = URLRequest(url: urlPollingWithSid)
|
||||
let postData = postStr.data(using: .utf8, allowLossyConversion: false)!
|
||||
|
||||
addHeaders(to: &req)
|
||||
|
||||
req.httpMethod = "POST"
|
||||
req.setValue("text/plain; charset=UTF-8", forHTTPHeaderField: "Content-Type")
|
||||
req.httpBody = postData
|
||||
req.setValue(String(postData.count), forHTTPHeaderField: "Content-Length")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
/// Call to send a long-polling request.
|
||||
///
|
||||
/// You shouldn't need to call this directly, the engine should automatically maintain a long-poll request.
|
||||
public func doPoll() {
|
||||
guard polling && !waitingForPoll && connected && !closed else { return }
|
||||
|
||||
var req = URLRequest(url: urlPollingWithSid)
|
||||
addHeaders(to: &req)
|
||||
|
||||
doLongPoll(for: req)
|
||||
}
|
||||
|
||||
func doRequest(for req: URLRequest, callbackWith callback: @escaping (Data?, URLResponse?, Error?) -> ()) {
|
||||
guard polling && !closed && !invalidated && !fastUpgrade else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Doing polling \(req.httpMethod ?? "") \(req)", type: "SocketEnginePolling")
|
||||
|
||||
session?.dataTask(with: req, completionHandler: callback).resume()
|
||||
}
|
||||
|
||||
func doLongPoll(for req: URLRequest) {
|
||||
waitingForPoll = true
|
||||
|
||||
doRequest(for: req) {[weak self] data, res, err in
|
||||
guard let this = self, this.polling else { return }
|
||||
|
||||
if err != nil || data == nil {
|
||||
DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling")
|
||||
|
||||
if this.polling {
|
||||
this.didError(reason: err?.localizedDescription ?? "Error")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Got polling response", type: "SocketEnginePolling")
|
||||
|
||||
if let str = String(data: data!, encoding: .utf8) {
|
||||
this.parsePollingMessage(str)
|
||||
}
|
||||
|
||||
this.waitingForPoll = false
|
||||
|
||||
if this.fastUpgrade {
|
||||
this.doFastUpgrade()
|
||||
} else if !this.closed && this.polling {
|
||||
this.doPoll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func flushWaitingForPost() {
|
||||
guard postWait.count != 0 && connected else { return }
|
||||
guard polling else {
|
||||
flushWaitingForPostToWebSocket()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let req = createRequestForPostWithPostWait()
|
||||
|
||||
waitingForPost = true
|
||||
|
||||
DefaultSocketLogger.Logger.log("POSTing", type: "SocketEnginePolling")
|
||||
|
||||
doRequest(for: req) {[weak self] data, res, err in
|
||||
guard let this = self else { return }
|
||||
|
||||
if err != nil {
|
||||
DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling")
|
||||
|
||||
if this.polling {
|
||||
this.didError(reason: err?.localizedDescription ?? "Error")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.waitingForPost = false
|
||||
|
||||
if !this.fastUpgrade {
|
||||
this.flushWaitingForPost()
|
||||
this.doPoll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parsePollingMessage(_ str: String) {
|
||||
guard str.count != 1 else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Got poll message: \(str)", type: "SocketEnginePolling")
|
||||
|
||||
var reader = SocketStringReader(message: str)
|
||||
|
||||
while reader.hasNext {
|
||||
if let n = Int(reader.readUntilOccurence(of: ":")) {
|
||||
parseEngineMessage(reader.read(count: n))
|
||||
} else {
|
||||
parseEngineMessage(str)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an engine.io message through the polling transport.
|
||||
///
|
||||
/// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
|
||||
///
|
||||
/// - parameter message: The message to send.
|
||||
/// - parameter withType: The type of message to send.
|
||||
/// - parameter withData: The data associated with this message.
|
||||
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
|
||||
DefaultSocketLogger.Logger.log("Sending poll: \(message) as type: \(type.rawValue)", type: "SocketEnginePolling")
|
||||
|
||||
postWait.append(String(type.rawValue) + message)
|
||||
|
||||
for data in datas {
|
||||
if case let .right(bin) = createBinaryDataForSend(using: data) {
|
||||
postWait.append(bin)
|
||||
}
|
||||
}
|
||||
|
||||
if !waitingForPost {
|
||||
flushWaitingForPost()
|
||||
}
|
||||
}
|
||||
|
||||
/// Call to stop polling and invalidate the URLSession.
|
||||
public func stopPolling() {
|
||||
waitingForPoll = false
|
||||
waitingForPost = false
|
||||
session?.finishTasksAndInvalidate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
// SocketEngineSpec.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 10/7/15.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Starscream
|
||||
|
||||
/// Specifies a SocketEngine.
|
||||
@objc public protocol SocketEngineSpec {
|
||||
// MARK: Properties
|
||||
|
||||
/// The client for this engine.
|
||||
var client: SocketEngineClient? { get set }
|
||||
|
||||
/// `true` if this engine is closed.
|
||||
var closed: Bool { get }
|
||||
|
||||
/// If `true` the engine will attempt to use WebSocket compression.
|
||||
var compress: Bool { get }
|
||||
|
||||
/// `true` if this engine is connected. Connected means that the initial poll connect has succeeded.
|
||||
var connected: Bool { get }
|
||||
|
||||
/// The connect parameters sent during a connect.
|
||||
var connectParams: [String: Any]? { get set }
|
||||
|
||||
/// An array of HTTPCookies that are sent during the connection.
|
||||
var cookies: [HTTPCookie]? { get }
|
||||
|
||||
/// The queue that all engine actions take place on.
|
||||
var engineQueue: DispatchQueue { get }
|
||||
|
||||
/// A dictionary of extra http headers that will be set during connection.
|
||||
var extraHeaders: [String: String]? { get set }
|
||||
|
||||
/// When `true`, the engine is in the process of switching to WebSockets.
|
||||
var fastUpgrade: Bool { get }
|
||||
|
||||
/// When `true`, the engine will only use HTTP long-polling as a transport.
|
||||
var forcePolling: Bool { get }
|
||||
|
||||
/// When `true`, the engine will only use WebSockets as a transport.
|
||||
var forceWebsockets: Bool { get }
|
||||
|
||||
/// If `true`, the engine is currently in HTTP long-polling mode.
|
||||
var polling: Bool { get }
|
||||
|
||||
/// If `true`, the engine is currently seeing whether it can upgrade to WebSockets.
|
||||
var probing: Bool { get }
|
||||
|
||||
/// The session id for this engine.
|
||||
var sid: String { get }
|
||||
|
||||
/// The path to engine.io.
|
||||
var socketPath: String { get }
|
||||
|
||||
/// The url for polling.
|
||||
var urlPolling: URL { get }
|
||||
|
||||
/// The url for WebSockets.
|
||||
var urlWebSocket: URL { get }
|
||||
|
||||
/// If `true`, then the engine is currently in WebSockets mode.
|
||||
@available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets")
|
||||
var websocket: Bool { get }
|
||||
|
||||
/// The WebSocket for this engine.
|
||||
var ws: WebSocket? { get }
|
||||
|
||||
// MARK: Initializers
|
||||
|
||||
/// Creates a new engine.
|
||||
///
|
||||
/// - parameter client: The client for this engine.
|
||||
/// - parameter url: The url for this engine.
|
||||
/// - parameter options: The options for this engine.
|
||||
init(client: SocketEngineClient, url: URL, options: [String: Any]?)
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
/// Starts the connection to the server.
|
||||
func connect()
|
||||
|
||||
/// Called when an error happens during execution. Causes a disconnection.
|
||||
func didError(reason: String)
|
||||
|
||||
/// Disconnects from the server.
|
||||
///
|
||||
/// - parameter reason: The reason for the disconnection. This is communicated up to the client.
|
||||
func disconnect(reason: String)
|
||||
|
||||
/// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in
|
||||
/// WebSocket mode.
|
||||
///
|
||||
/// **You shouldn't call this directly**
|
||||
func doFastUpgrade()
|
||||
|
||||
/// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when
|
||||
/// the engine is attempting to upgrade to WebSocket it does not do any POSTing.
|
||||
///
|
||||
/// **You shouldn't call this directly**
|
||||
func flushWaitingForPostToWebSocket()
|
||||
|
||||
/// Parses raw binary received from engine.io.
|
||||
///
|
||||
/// - parameter data: The data to parse.
|
||||
func parseEngineData(_ data: Data)
|
||||
|
||||
/// Parses a raw engine.io packet.
|
||||
///
|
||||
/// - parameter message: The message to parse.
|
||||
func parseEngineMessage(_ message: String)
|
||||
|
||||
/// Writes a message to engine.io, independent of transport.
|
||||
///
|
||||
/// - parameter msg: The message to send.
|
||||
/// - parameter type: The type of this message.
|
||||
/// - parameter data: Any data that this message has.
|
||||
func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data])
|
||||
}
|
||||
|
||||
extension SocketEngineSpec {
|
||||
var urlPollingWithSid: URL {
|
||||
var com = URLComponents(url: urlPolling, resolvingAgainstBaseURL: false)!
|
||||
com.percentEncodedQuery = com.percentEncodedQuery! + "&sid=\(sid.urlEncode()!)"
|
||||
|
||||
return com.url!
|
||||
}
|
||||
|
||||
var urlWebSocketWithSid: URL {
|
||||
var com = URLComponents(url: urlWebSocket, resolvingAgainstBaseURL: false)!
|
||||
com.percentEncodedQuery = com.percentEncodedQuery! + (sid == "" ? "" : "&sid=\(sid.urlEncode()!)")
|
||||
|
||||
return com.url!
|
||||
}
|
||||
|
||||
func addHeaders(to req: inout URLRequest, includingCookies additionalCookies: [HTTPCookie]? = nil) {
|
||||
var cookiesToAdd: [HTTPCookie] = cookies ?? []
|
||||
cookiesToAdd += additionalCookies ?? []
|
||||
|
||||
if !cookiesToAdd.isEmpty {
|
||||
req.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookiesToAdd)
|
||||
}
|
||||
|
||||
if let extraHeaders = extraHeaders {
|
||||
for (headerName, value) in extraHeaders {
|
||||
req.setValue(value, forHTTPHeaderField: headerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createBinaryDataForSend(using data: Data) -> Either<Data, String> {
|
||||
if polling {
|
||||
return .right("b4" + data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0)))
|
||||
} else {
|
||||
return .left(Data(bytes: [0x4]) + data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Send an engine message (4)
|
||||
func send(_ msg: String, withData datas: [Data]) {
|
||||
write(msg, withType: .message, withData: datas)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// SocketEngineWebsocket.swift
|
||||
// Socket.IO-Client-Swift
|
||||
//
|
||||
// Created by Erik Little on 1/15/16.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Starscream
|
||||
|
||||
/// Protocol that is used to implement socket.io WebSocket support
|
||||
public protocol SocketEngineWebsocket : SocketEngineSpec {
|
||||
// MARK: Methods
|
||||
|
||||
/// Sends an engine.io message through the WebSocket transport.
|
||||
///
|
||||
/// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
|
||||
///
|
||||
/// - parameter message: The message to send.
|
||||
/// - parameter withType: The type of message to send.
|
||||
/// - parameter withData: The data associated with this message.
|
||||
func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data])
|
||||
}
|
||||
|
||||
// WebSocket methods
|
||||
extension SocketEngineWebsocket {
|
||||
func probeWebSocket() {
|
||||
if ws?.isConnected ?? false {
|
||||
sendWebSocketMessage("probe", withType: .ping, withData: [])
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an engine.io message through the WebSocket transport.
|
||||
///
|
||||
/// You shouldn't call this directly, instead call the `write` method on `SocketEngine`.
|
||||
///
|
||||
/// - parameter message: The message to send.
|
||||
/// - parameter withType: The type of message to send.
|
||||
/// - parameter withData: The data associated with this message.
|
||||
public func sendWebSocketMessage(_ str: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
|
||||
DefaultSocketLogger.Logger.log("Sending ws: \(str) as type: \(type.rawValue)", type: "SocketEngineWebSocket")
|
||||
|
||||
ws?.write(string: "\(type.rawValue)\(str)")
|
||||
|
||||
for data in datas {
|
||||
if case let .left(bin) = createBinaryDataForSend(using: data) {
|
||||
ws?.write(data: bin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user