You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

765 lines
25 KiB

2 years ago
  1. //
  2. // SocketEngine.swift
  3. // Socket.IO-Client-Swift
  4. //
  5. // Created by Erik Little on 3/3/15.
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. import Dispatch
  25. import Foundation
  26. import Starscream
  27. /// The class that handles the engine.io protocol and transports.
  28. /// See `SocketEnginePollable` and `SocketEngineWebsocket` for transport specific methods.
  29. open class SocketEngine:
  30. NSObject, WebSocketDelegate, URLSessionDelegate, SocketEnginePollable, SocketEngineWebsocket, ConfigSettable {
  31. // MARK: Properties
  32. private static let logType = "SocketEngine"
  33. /// The queue that all engine actions take place on.
  34. public let engineQueue = DispatchQueue(label: "com.socketio.engineHandleQueue")
  35. /// The connect parameters sent during a connect.
  36. public var connectParams: [String: Any]? {
  37. didSet {
  38. (urlPolling, urlWebSocket) = createURLs()
  39. }
  40. }
  41. /// A dictionary of extra http headers that will be set during connection.
  42. public var extraHeaders: [String: String]?
  43. /// A queue of engine.io messages waiting for POSTing
  44. ///
  45. /// **You should not touch this directly**
  46. public var postWait = [Post]()
  47. /// `true` if there is an outstanding poll. Trying to poll before the first is done will cause socket.io to
  48. /// disconnect us.
  49. ///
  50. /// **Do not touch this directly**
  51. public var waitingForPoll = false
  52. /// `true` if there is an outstanding post. Trying to post before the first is done will cause socket.io to
  53. /// disconnect us.
  54. ///
  55. /// **Do not touch this directly**
  56. public var waitingForPost = false
  57. /// `true` if this engine is closed.
  58. public private(set) var closed = false
  59. /// If `true` the engine will attempt to use WebSocket compression.
  60. public private(set) var compress = false
  61. /// `true` if this engine is connected. Connected means that the initial poll connect has succeeded.
  62. public private(set) var connected = false
  63. /// An array of HTTPCookies that are sent during the connection.
  64. public private(set) var cookies: [HTTPCookie]?
  65. /// When `true`, the engine is in the process of switching to WebSockets.
  66. ///
  67. /// **Do not touch this directly**
  68. public private(set) var fastUpgrade = false
  69. /// When `true`, the engine will only use HTTP long-polling as a transport.
  70. public private(set) var forcePolling = false
  71. /// When `true`, the engine will only use WebSockets as a transport.
  72. public private(set) var forceWebsockets = false
  73. /// `true` If engine's session has been invalidated.
  74. public private(set) var invalidated = false
  75. /// If `true`, the engine is currently in HTTP long-polling mode.
  76. public private(set) var polling = true
  77. /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets.
  78. public private(set) var probing = false
  79. /// The URLSession that will be used for polling.
  80. public private(set) var session: URLSession?
  81. /// The session id for this engine.
  82. public private(set) var sid = ""
  83. /// The path to engine.io.
  84. public private(set) var socketPath = "/engine.io/"
  85. /// The url for polling.
  86. public private(set) var urlPolling = URL(string: "http://localhost/")!
  87. /// The url for WebSockets.
  88. public private(set) var urlWebSocket = URL(string: "http://localhost/")!
  89. /// The version of engine.io being used. Default is three.
  90. public private(set) var version: SocketIOVersion = .three
  91. /// If `true`, then the engine is currently in WebSockets mode.
  92. @available(*, deprecated, message: "No longer needed, if we're not polling, then we must be doing websockets")
  93. public private(set) var websocket = false
  94. /// When `true`, the WebSocket `stream` will be configured with the enableSOCKSProxy `true`.
  95. public private(set) var enableSOCKSProxy = false
  96. /// The WebSocket for this engine.
  97. public private(set) var ws: WebSocket?
  98. /// Whether or not the WebSocket is currently connected.
  99. public private(set) var wsConnected = false
  100. /// The client for this engine.
  101. public weak var client: SocketEngineClient?
  102. private weak var sessionDelegate: URLSessionDelegate?
  103. private let url: URL
  104. private var lastCommunication: Date?
  105. private var pingInterval: Int?
  106. private var pingTimeout = 0 {
  107. didSet {
  108. pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25000))
  109. }
  110. }
  111. private var pongsMissed = 0
  112. private var pongsMissedMax = 0
  113. private var probeWait = ProbeWaitQueue()
  114. private var secure = false
  115. private var certPinner: CertificatePinning?
  116. private var selfSigned = false
  117. // MARK: Initializers
  118. /// Creates a new engine.
  119. ///
  120. /// - parameter client: The client for this engine.
  121. /// - parameter url: The url for this engine.
  122. /// - parameter config: An array of configuration options for this engine.
  123. public init(client: SocketEngineClient, url: URL, config: SocketIOClientConfiguration) {
  124. self.client = client
  125. self.url = url
  126. super.init()
  127. setConfigs(config)
  128. sessionDelegate = sessionDelegate ?? self
  129. (urlPolling, urlWebSocket) = createURLs()
  130. }
  131. /// Creates a new engine.
  132. ///
  133. /// - parameter client: The client for this engine.
  134. /// - parameter url: The url for this engine.
  135. /// - parameter options: The options for this engine.
  136. public required convenience init(client: SocketEngineClient, url: URL, options: [String: Any]?) {
  137. self.init(client: client, url: url, config: options?.toSocketConfiguration() ?? [])
  138. }
  139. /// :nodoc:
  140. deinit {
  141. DefaultSocketLogger.Logger.log("Engine is being released", type: SocketEngine.logType)
  142. closed = true
  143. stopPolling()
  144. }
  145. // MARK: Methods
  146. private func checkAndHandleEngineError(_ msg: String) {
  147. do {
  148. let dict = try msg.toDictionary()
  149. guard let error = dict["message"] as? String else { return }
  150. /*
  151. 0: Unknown transport
  152. 1: Unknown sid
  153. 2: Bad handshake request
  154. 3: Bad request
  155. */
  156. didError(reason: error)
  157. } catch {
  158. client?.engineDidError(reason: "Got unknown error from server \(msg)")
  159. }
  160. }
  161. private func handleBase64(message: String) {
  162. let offset = version.rawValue >= 3 ? 1 : 2
  163. // binary in base64 string
  164. let noPrefix = String(message[message.index(message.startIndex, offsetBy: offset)..<message.endIndex])
  165. if let data = Data(base64Encoded: noPrefix, options: .ignoreUnknownCharacters) {
  166. client?.parseEngineBinaryData(data)
  167. }
  168. }
  169. private func closeOutEngine(reason: String) {
  170. sid = ""
  171. closed = true
  172. invalidated = true
  173. connected = false
  174. ws?.disconnect()
  175. stopPolling()
  176. client?.engineDidClose(reason: reason)
  177. }
  178. /// Starts the connection to the server.
  179. open func connect() {
  180. engineQueue.async {
  181. self._connect()
  182. }
  183. }
  184. private func _connect() {
  185. if connected {
  186. DefaultSocketLogger.Logger.error("Engine tried opening while connected. Assuming this was a reconnect",
  187. type: SocketEngine.logType)
  188. _disconnect(reason: "reconnect")
  189. }
  190. DefaultSocketLogger.Logger.log("Starting engine. Server: \(url)", type: SocketEngine.logType)
  191. DefaultSocketLogger.Logger.log("Handshaking", type: SocketEngine.logType)
  192. resetEngine()
  193. if forceWebsockets {
  194. polling = false
  195. createWebSocketAndConnect()
  196. return
  197. }
  198. var reqPolling = URLRequest(url: urlPolling, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
  199. addHeaders(to: &reqPolling)
  200. doLongPoll(for: reqPolling)
  201. }
  202. private func createURLs() -> (URL, URL) {
  203. if client == nil {
  204. return (URL(string: "http://localhost/")!, URL(string: "http://localhost/")!)
  205. }
  206. var urlPolling = URLComponents(string: url.absoluteString)!
  207. var urlWebSocket = URLComponents(string: url.absoluteString)!
  208. var queryString = ""
  209. urlWebSocket.path = socketPath
  210. urlPolling.path = socketPath
  211. if secure {
  212. urlPolling.scheme = "https"
  213. urlWebSocket.scheme = "wss"
  214. } else {
  215. urlPolling.scheme = "http"
  216. urlWebSocket.scheme = "ws"
  217. }
  218. if let connectParams = self.connectParams {
  219. for (key, value) in connectParams {
  220. let keyEsc = key.urlEncode()!
  221. let valueEsc = "\(value)".urlEncode()!
  222. queryString += "&\(keyEsc)=\(valueEsc)"
  223. }
  224. }
  225. urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString
  226. urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString
  227. if !urlWebSocket.percentEncodedQuery!.contains("EIO") {
  228. urlWebSocket.percentEncodedQuery = urlWebSocket.percentEncodedQuery! + engineIOParam
  229. }
  230. if !urlPolling.percentEncodedQuery!.contains("EIO") {
  231. urlPolling.percentEncodedQuery = urlPolling.percentEncodedQuery! + engineIOParam
  232. }
  233. return (urlPolling.url!, urlWebSocket.url!)
  234. }
  235. private func createWebSocketAndConnect() {
  236. var req = URLRequest(url: urlWebSocketWithSid)
  237. addHeaders(
  238. to: &req,
  239. includingCookies: session?.configuration.httpCookieStorage?.cookies(for: urlPollingWithSid)
  240. )
  241. ws = WebSocket(request: req, certPinner: certPinner, compressionHandler: compress ? WSCompression() : nil)
  242. ws?.callbackQueue = engineQueue
  243. ws?.delegate = self
  244. ws?.connect()
  245. }
  246. /// Called when an error happens during execution. Causes a disconnection.
  247. open func didError(reason: String) {
  248. DefaultSocketLogger.Logger.error("\(reason)", type: SocketEngine.logType)
  249. client?.engineDidError(reason: reason)
  250. disconnect(reason: reason)
  251. }
  252. /// Disconnects from the server.
  253. ///
  254. /// - parameter reason: The reason for the disconnection. This is communicated up to the client.
  255. open func disconnect(reason: String) {
  256. engineQueue.async {
  257. self._disconnect(reason: reason)
  258. }
  259. }
  260. private func _disconnect(reason: String) {
  261. guard connected && !closed else { return closeOutEngine(reason: reason) }
  262. DefaultSocketLogger.Logger.log("Engine is being closed.", type: SocketEngine.logType)
  263. if polling {
  264. disconnectPolling(reason: reason)
  265. } else {
  266. sendWebSocketMessage("", withType: .close, withData: [], completion: nil)
  267. closeOutEngine(reason: reason)
  268. }
  269. }
  270. // We need to take special care when we're polling that we send it ASAP
  271. // Also make sure we're on the emitQueue since we're touching postWait
  272. private func disconnectPolling(reason: String) {
  273. postWait.append((String(SocketEnginePacketType.close.rawValue), {}))
  274. doRequest(for: createRequestForPostWithPostWait()) {_, _, _ in }
  275. closeOutEngine(reason: reason)
  276. }
  277. /// Called to switch from HTTP long-polling to WebSockets. After calling this method the engine will be in
  278. /// WebSocket mode.
  279. ///
  280. /// **You shouldn't call this directly**
  281. open func doFastUpgrade() {
  282. if waitingForPoll {
  283. DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," +
  284. "we'll probably disconnect soon. You should report this.", type: SocketEngine.logType)
  285. }
  286. DefaultSocketLogger.Logger.log("Switching to WebSockets", type: SocketEngine.logType)
  287. sendWebSocketMessage("", withType: .upgrade, withData: [], completion: nil)
  288. polling = false
  289. fastUpgrade = false
  290. probing = false
  291. flushProbeWait()
  292. // Need to flush postWait to socket since it connected successfully
  293. // moved from flushProbeWait() since it is also called on connected failure, and we don't want to try and send
  294. // packets through WebSockets when WebSockets has failed!
  295. if !postWait.isEmpty {
  296. flushWaitingForPostToWebSocket()
  297. }
  298. }
  299. private func flushProbeWait() {
  300. DefaultSocketLogger.Logger.log("Flushing probe wait", type: SocketEngine.logType)
  301. for waiter in probeWait {
  302. write(waiter.msg, withType: waiter.type, withData: waiter.data, completion: waiter.completion)
  303. }
  304. probeWait.removeAll(keepingCapacity: false)
  305. }
  306. /// Causes any packets that were waiting for POSTing to be sent through the WebSocket. This happens because when
  307. /// the engine is attempting to upgrade to WebSocket it does not do any POSTing.
  308. ///
  309. /// **You shouldn't call this directly**
  310. open func flushWaitingForPostToWebSocket() {
  311. guard let ws = self.ws else { return }
  312. for msg in postWait {
  313. ws.write(string: msg.msg, completion: msg.completion)
  314. }
  315. postWait.removeAll(keepingCapacity: false)
  316. }
  317. private func handleClose(_ reason: String) {
  318. client?.engineDidClose(reason: reason)
  319. }
  320. private func handleMessage(_ message: String) {
  321. client?.parseEngineMessage(message)
  322. }
  323. private func handleNOOP() {
  324. doPoll()
  325. }
  326. private func handleOpen(openData: String) {
  327. guard let json = try? openData.toDictionary() else {
  328. didError(reason: "Error parsing open packet")
  329. return
  330. }
  331. guard let sid = json["sid"] as? String else {
  332. didError(reason: "Open packet contained no sid")
  333. return
  334. }
  335. let upgradeWs: Bool
  336. self.sid = sid
  337. connected = true
  338. pongsMissed = 0
  339. if let upgrades = json["upgrades"] as? [String] {
  340. upgradeWs = upgrades.contains("websocket")
  341. } else {
  342. upgradeWs = false
  343. }
  344. if let pingInterval = json["pingInterval"] as? Int, let pingTimeout = json["pingTimeout"] as? Int {
  345. self.pingInterval = pingInterval
  346. self.pingTimeout = pingTimeout
  347. }
  348. if !forcePolling && !forceWebsockets && upgradeWs {
  349. createWebSocketAndConnect()
  350. }
  351. if version.rawValue >= 3 {
  352. checkPings()
  353. } else {
  354. sendPing()
  355. }
  356. if !forceWebsockets {
  357. doPoll()
  358. }
  359. client?.engineDidOpen(reason: "Connect")
  360. }
  361. private func handlePong(with message: String) {
  362. pongsMissed = 0
  363. // We should upgrade
  364. if message == "3probe" {
  365. DefaultSocketLogger.Logger.log("Received probe response, should upgrade to WebSockets",
  366. type: SocketEngine.logType)
  367. upgradeTransport()
  368. }
  369. client?.engineDidReceivePong()
  370. }
  371. private func handlePing(with message: String) {
  372. if version.rawValue >= 3 {
  373. write("", withType: .pong, withData: [])
  374. }
  375. client?.engineDidReceivePing()
  376. }
  377. private func checkPings() {
  378. let pingInterval = self.pingInterval ?? 25_000
  379. let deadlineMs = Double(pingInterval + pingTimeout) / 1000
  380. let timeoutDeadline = DispatchTime.now() + .milliseconds(pingInterval + pingTimeout)
  381. engineQueue.asyncAfter(deadline: timeoutDeadline) {[weak self, id = self.sid] in
  382. // Make sure not to ping old connections
  383. guard let this = self, this.sid == id else { return }
  384. if abs(this.lastCommunication?.timeIntervalSinceNow ?? deadlineMs) >= deadlineMs {
  385. this.closeOutEngine(reason: "Ping timeout")
  386. } else {
  387. this.checkPings()
  388. }
  389. }
  390. }
  391. /// Parses raw binary received from engine.io.
  392. ///
  393. /// - parameter data: The data to parse.
  394. open func parseEngineData(_ data: Data) {
  395. DefaultSocketLogger.Logger.log("Got binary data: \(data)", type: SocketEngine.logType)
  396. lastCommunication = Date()
  397. client?.parseEngineBinaryData(version.rawValue >= 3 ? data : data.subdata(in: 1..<data.endIndex))
  398. }
  399. /// Parses a raw engine.io packet.
  400. ///
  401. /// - parameter message: The message to parse.
  402. open func parseEngineMessage(_ message: String) {
  403. lastCommunication = Date()
  404. DefaultSocketLogger.Logger.log("Got message: \(message)", type: SocketEngine.logType)
  405. if message.hasPrefix(version.rawValue >= 3 ? "b" : "b4") {
  406. return handleBase64(message: message)
  407. }
  408. guard let type = SocketEnginePacketType(rawValue: message.first?.wholeNumberValue ?? -1) else {
  409. checkAndHandleEngineError(message)
  410. return
  411. }
  412. switch type {
  413. case .message:
  414. handleMessage(String(message.dropFirst()))
  415. case .noop:
  416. handleNOOP()
  417. case .ping:
  418. handlePing(with: message)
  419. case .pong:
  420. handlePong(with: message)
  421. case .open:
  422. handleOpen(openData: String(message.dropFirst()))
  423. case .close:
  424. handleClose(message)
  425. default:
  426. DefaultSocketLogger.Logger.log("Got unknown packet type", type: SocketEngine.logType)
  427. }
  428. }
  429. // Puts the engine back in its default state
  430. private func resetEngine() {
  431. let queue = OperationQueue()
  432. queue.underlyingQueue = engineQueue
  433. closed = false
  434. connected = false
  435. fastUpgrade = false
  436. polling = true
  437. probing = false
  438. invalidated = false
  439. session = Foundation.URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: queue)
  440. sid = ""
  441. waitingForPoll = false
  442. waitingForPost = false
  443. }
  444. private func sendPing() {
  445. guard connected, let pingInterval = pingInterval else {
  446. return
  447. }
  448. // Server is not responding
  449. if pongsMissed > pongsMissedMax {
  450. closeOutEngine(reason: "Ping timeout")
  451. return
  452. }
  453. pongsMissed += 1
  454. write("", withType: .ping, withData: [], completion: nil)
  455. engineQueue.asyncAfter(deadline: .now() + .milliseconds(pingInterval)) {[weak self, id = self.sid] in
  456. // Make sure not to ping old connections
  457. guard let this = self, this.sid == id else {
  458. return
  459. }
  460. this.sendPing()
  461. }
  462. client?.engineDidSendPing()
  463. }
  464. /// Called when the engine should set/update its configs from a given configuration.
  465. ///
  466. /// parameter config: The `SocketIOClientConfiguration` that should be used to set/update configs.
  467. open func setConfigs(_ config: SocketIOClientConfiguration) {
  468. for option in config {
  469. switch option {
  470. case let .connectParams(params):
  471. connectParams = params
  472. case let .cookies(cookies):
  473. self.cookies = cookies
  474. case let .extraHeaders(headers):
  475. extraHeaders = headers
  476. case let .sessionDelegate(delegate):
  477. sessionDelegate = delegate
  478. case let .forcePolling(force):
  479. forcePolling = force
  480. case let .forceWebsockets(force):
  481. forceWebsockets = force
  482. case let .path(path):
  483. socketPath = path
  484. if !socketPath.hasSuffix("/") {
  485. socketPath += "/"
  486. }
  487. case let .secure(secure):
  488. self.secure = secure
  489. case let .selfSigned(selfSigned):
  490. self.selfSigned = selfSigned
  491. case let .security(pinner):
  492. self.certPinner = pinner
  493. case .compress:
  494. self.compress = true
  495. case .enableSOCKSProxy:
  496. self.enableSOCKSProxy = true
  497. case let .version(num):
  498. version = num
  499. default:
  500. continue
  501. }
  502. }
  503. }
  504. // Moves from long-polling to websockets
  505. private func upgradeTransport() {
  506. if wsConnected {
  507. DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: SocketEngine.logType)
  508. fastUpgrade = true
  509. sendPollMessage("", withType: .noop, withData: [], completion: nil)
  510. // After this point, we should not send anymore polling messages
  511. }
  512. }
  513. /// Writes a message to engine.io, independent of transport.
  514. ///
  515. /// - parameter msg: The message to send.
  516. /// - parameter type: The type of this message.
  517. /// - parameter data: Any data that this message has.
  518. /// - parameter completion: Callback called on transport write completion.
  519. open func write(_ msg: String, withType type: SocketEnginePacketType, withData data: [Data], completion: (() -> ())? = nil) {
  520. engineQueue.async {
  521. guard self.connected else {
  522. completion?()
  523. return
  524. }
  525. guard !self.probing else {
  526. self.probeWait.append((msg, type, data, completion))
  527. return
  528. }
  529. if self.polling {
  530. DefaultSocketLogger.Logger.log("Writing poll: \(msg) has data: \(data.count != 0)",
  531. type: SocketEngine.logType)
  532. self.sendPollMessage(msg, withType: type, withData: data, completion: completion)
  533. } else {
  534. DefaultSocketLogger.Logger.log("Writing ws: \(msg) has data: \(data.count != 0)",
  535. type: SocketEngine.logType)
  536. self.sendWebSocketMessage(msg, withType: type, withData: data, completion: completion)
  537. }
  538. }
  539. }
  540. // WebSocket Methods
  541. private func websocketDidConnect() {
  542. if !forceWebsockets {
  543. probing = true
  544. probeWebSocket()
  545. } else {
  546. connected = true
  547. probing = false
  548. polling = false
  549. }
  550. }
  551. private func websocketDidDisconnect(error: Error?) {
  552. probing = false
  553. if closed {
  554. client?.engineDidClose(reason: "Disconnect")
  555. return
  556. }
  557. guard !polling else {
  558. flushProbeWait()
  559. return
  560. }
  561. connected = false
  562. polling = true
  563. if let error = error as? WSError {
  564. didError(reason: "\(error.message). code=\(error.code), type=\(error.type)")
  565. } else if let reason = error?.localizedDescription {
  566. didError(reason: reason)
  567. } else {
  568. client?.engineDidClose(reason: "Socket Disconnected")
  569. }
  570. }
  571. // Test Properties
  572. func setConnected(_ value: Bool) {
  573. connected = value
  574. }
  575. }
  576. extension SocketEngine {
  577. // MARK: URLSessionDelegate methods
  578. /// Delegate called when the session becomes invalid.
  579. public func URLSession(session: URLSession, didBecomeInvalidWithError error: NSError?) {
  580. DefaultSocketLogger.Logger.error("Engine URLSession became invalid", type: "SocketEngine")
  581. didError(reason: "Engine URLSession became invalid")
  582. }
  583. }
  584. enum EngineError: Error {
  585. case canceled
  586. }
  587. extension SocketEngine {
  588. /// Delegate method for WebSocketDelegate.
  589. ///
  590. /// - Parameters:
  591. /// - event: WS Event
  592. /// - _:
  593. public func didReceive(event: WebSocketEvent, client _: WebSocket) {
  594. switch event {
  595. case let .connected(headers):
  596. wsConnected = true
  597. client?.engineDidWebsocketUpgrade(headers: headers)
  598. websocketDidConnect()
  599. case .cancelled:
  600. wsConnected = false
  601. websocketDidDisconnect(error: EngineError.canceled)
  602. case let .disconnected(reason, code):
  603. wsConnected = false
  604. websocketDidDisconnect(error: nil)
  605. case let .text(msg):
  606. parseEngineMessage(msg)
  607. case let .binary(data):
  608. parseEngineData(data)
  609. case _:
  610. break
  611. }
  612. }
  613. }