SocketIOClient.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. //
  2. // SocketIOClient.swift
  3. // Socket.IO-Client-Swift
  4. //
  5. // Created by Erik Little on 11/23/14.
  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. /// Represents a socket.io-client.
  27. ///
  28. /// Clients are created through a `SocketManager`, which owns the `SocketEngineSpec` that controls the connection to the server.
  29. ///
  30. /// For example:
  31. ///
  32. /// ```swift
  33. /// // Create a socket for the /swift namespace
  34. /// let socket = manager.socket(forNamespace: "/swift")
  35. ///
  36. /// // Add some handlers and connect
  37. /// ```
  38. ///
  39. /// **NOTE**: The client is not thread/queue safe, all interaction with the socket should be done on the `manager.handleQueue`
  40. ///
  41. open class SocketIOClient : NSObject, SocketIOClientSpec {
  42. // MARK: Properties
  43. /// The namespace that this socket is currently connected to.
  44. ///
  45. /// **Must** start with a `/`.
  46. @objc
  47. public let nsp: String
  48. /// The session id of this client.
  49. @objc
  50. public var sid: String {
  51. guard let engine = manager?.engine else { return "" }
  52. return nsp == "/" ? engine.sid : "\(nsp)#\(engine.sid)"
  53. }
  54. /// A handler that will be called on any event.
  55. public private(set) var anyHandler: ((SocketAnyEvent) -> ())?
  56. /// The array of handlers for this socket.
  57. public private(set) var handlers = [SocketEventHandler]()
  58. /// The manager for this socket.
  59. @objc
  60. public private(set) weak var manager: SocketManagerSpec?
  61. /// A view into this socket where emits do not check for binary data.
  62. ///
  63. /// Usage:
  64. ///
  65. /// ```swift
  66. /// socket.rawEmitView.emit("myEvent", myObject)
  67. /// ```
  68. ///
  69. /// **NOTE**: It is not safe to hold on to this view beyond the life of the socket.
  70. @objc
  71. public private(set) lazy var rawEmitView = SocketRawView(socket: self)
  72. /// The status of this client.
  73. @objc
  74. public private(set) var status = SocketIOStatus.notConnected {
  75. didSet {
  76. handleClientEvent(.statusChange, data: [status, status.rawValue])
  77. }
  78. }
  79. let ackHandlers = SocketAckManager()
  80. private(set) var currentAck = -1
  81. private lazy var logType = "SocketIOClient{\(nsp)}"
  82. // MARK: Initializers
  83. /// Type safe way to create a new SocketIOClient. `opts` can be omitted.
  84. ///
  85. /// - parameter manager: The manager for this socket.
  86. /// - parameter nsp: The namespace of the socket.
  87. @objc
  88. public init(manager: SocketManagerSpec, nsp: String) {
  89. self.manager = manager
  90. self.nsp = nsp
  91. super.init()
  92. }
  93. /// :nodoc:
  94. deinit {
  95. DefaultSocketLogger.Logger.log("Client is being released", type: logType)
  96. }
  97. // MARK: Methods
  98. /// Connect to the server. The same as calling `connect(timeoutAfter:withHandler:)` with a timeout of 0.
  99. ///
  100. /// Only call after adding your event listeners, unless you know what you're doing.
  101. @objc
  102. open func connect() {
  103. connect(timeoutAfter: 0, withHandler: nil)
  104. }
  105. /// Connect to the server. If we aren't connected after `timeoutAfter` seconds, then `withHandler` is called.
  106. ///
  107. /// Only call after adding your event listeners, unless you know what you're doing.
  108. ///
  109. /// - parameter timeoutAfter: The number of seconds after which if we are not connected we assume the connection
  110. /// has failed. Pass 0 to never timeout.
  111. /// - parameter handler: The handler to call when the client fails to connect.
  112. @objc
  113. open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?) {
  114. assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")
  115. guard let manager = self.manager, status != .connected else {
  116. DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket", type: logType)
  117. return
  118. }
  119. status = .connecting
  120. joinNamespace()
  121. if manager.status == .connected && nsp == "/" {
  122. // We might not get a connect event for the default nsp, fire immediately
  123. didConnect(toNamespace: nsp)
  124. return
  125. }
  126. guard timeoutAfter != 0 else { return }
  127. manager.handleQueue.asyncAfter(deadline: DispatchTime.now() + timeoutAfter) {[weak self] in
  128. guard let this = self, this.status == .connecting || this.status == .notConnected else { return }
  129. this.status = .disconnected
  130. this.leaveNamespace()
  131. handler?()
  132. }
  133. }
  134. func createOnAck(_ items: [Any], binary: Bool = true) -> OnAckCallback {
  135. currentAck += 1
  136. return OnAckCallback(ackNumber: currentAck, items: items, socket: self)
  137. }
  138. /// Called when the client connects to a namespace. If the client was created with a namespace upfront,
  139. /// then this is only called when the client connects to that namespace.
  140. ///
  141. /// - parameter toNamespace: The namespace that was connected to.
  142. open func didConnect(toNamespace namespace: String) {
  143. guard status != .connected else { return }
  144. DefaultSocketLogger.Logger.log("Socket connected", type: logType)
  145. status = .connected
  146. handleClientEvent(.connect, data: [namespace])
  147. }
  148. /// Called when the client has disconnected from socket.io.
  149. ///
  150. /// - parameter reason: The reason for the disconnection.
  151. open func didDisconnect(reason: String) {
  152. guard status != .disconnected else { return }
  153. DefaultSocketLogger.Logger.log("Disconnected: \(reason)", type: logType)
  154. status = .disconnected
  155. handleClientEvent(.disconnect, data: [reason])
  156. }
  157. /// Disconnects the socket.
  158. ///
  159. /// This will cause the socket to leave the namespace it is associated to, as well as remove itself from the
  160. /// `manager`.
  161. @objc
  162. open func disconnect() {
  163. DefaultSocketLogger.Logger.log("Closing socket", type: logType)
  164. leaveNamespace()
  165. }
  166. /// Send an event to the server, with optional data items and optional write completion handler.
  167. ///
  168. /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
  169. /// will be emitted. The structure of the error data is `[eventName, items, theError]`
  170. ///
  171. /// - parameter event: The event to send.
  172. /// - parameter items: The items to send with this event. May be left out.
  173. /// - parameter completion: Callback called on transport write completion.
  174. open func emit(_ event: String, _ items: SocketData..., completion: (() -> ())? = nil) {
  175. do {
  176. try emit(event, with: items.map({ try $0.socketRepresentation() }), completion: completion)
  177. } catch {
  178. DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
  179. type: logType)
  180. handleClientEvent(.error, data: [event, items, error])
  181. }
  182. }
  183. /// Same as emit, but meant for Objective-C
  184. ///
  185. /// - parameter event: The event to send.
  186. /// - parameter items: The items to send with this event. Send an empty array to send no data.
  187. @objc
  188. open func emit(_ event: String, with items: [Any]) {
  189. emit([event] + items)
  190. }
  191. /// Same as emit, but meant for Objective-C
  192. ///
  193. /// - parameter event: The event to send.
  194. /// - parameter items: The items to send with this event. Send an empty array to send no data.
  195. /// - parameter completion: Callback called on transport write completion.
  196. @objc
  197. open func emit(_ event: String, with items: [Any], completion: (() -> ())? = nil) {
  198. emit([event] + items, completion: completion)
  199. }
  200. /// Sends a message to the server, requesting an ack.
  201. ///
  202. /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack.
  203. /// Check that your server's api will ack the event being sent.
  204. ///
  205. /// If an error occurs trying to transform `items` into their socket representation, a `SocketClientEvent.error`
  206. /// will be emitted. The structure of the error data is `[eventName, items, theError]`
  207. ///
  208. /// Example:
  209. ///
  210. /// ```swift
  211. /// socket.emitWithAck("myEvent", 1).timingOut(after: 1) {data in
  212. /// ...
  213. /// }
  214. /// ```
  215. ///
  216. /// - parameter event: The event to send.
  217. /// - parameter items: The items to send with this event. May be left out.
  218. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent.
  219. open func emitWithAck(_ event: String, _ items: SocketData...) -> OnAckCallback {
  220. do {
  221. return emitWithAck(event, with: try items.map({ try $0.socketRepresentation() }))
  222. } catch {
  223. DefaultSocketLogger.Logger.error("Error creating socketRepresentation for emit: \(event), \(items)",
  224. type: logType)
  225. handleClientEvent(.error, data: [event, items, error])
  226. return OnAckCallback(ackNumber: -1, items: [], socket: self)
  227. }
  228. }
  229. /// Same as emitWithAck, but for Objective-C
  230. ///
  231. /// **NOTE**: It is up to the server send an ack back, just calling this method does not mean the server will ack.
  232. /// Check that your server's api will ack the event being sent.
  233. ///
  234. /// Example:
  235. ///
  236. /// ```swift
  237. /// socket.emitWithAck("myEvent", with: [1]).timingOut(after: 1) {data in
  238. /// ...
  239. /// }
  240. /// ```
  241. ///
  242. /// - parameter event: The event to send.
  243. /// - parameter items: The items to send with this event. Use `[]` to send nothing.
  244. /// - returns: An `OnAckCallback`. You must call the `timingOut(after:)` method before the event will be sent.
  245. @objc
  246. open func emitWithAck(_ event: String, with items: [Any]) -> OnAckCallback {
  247. return createOnAck([event] + items)
  248. }
  249. func emit(_ data: [Any],
  250. ack: Int? = nil,
  251. binary: Bool = true,
  252. isAck: Bool = false,
  253. completion: (() -> ())? = nil
  254. ) {
  255. // wrap the completion handler so it always runs async via handlerQueue
  256. let wrappedCompletion: (() -> ())? = (completion == nil) ? nil : {[weak self] in
  257. guard let this = self else { return }
  258. this.manager?.handleQueue.async {
  259. completion!()
  260. }
  261. }
  262. guard status == .connected else {
  263. wrappedCompletion?()
  264. handleClientEvent(.error, data: ["Tried emitting when not connected"])
  265. return
  266. }
  267. let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: nsp, ack: isAck, checkForBinary: binary)
  268. let str = packet.packetString
  269. DefaultSocketLogger.Logger.log("Emitting: \(str), Ack: \(isAck)", type: logType)
  270. manager?.engine?.send(str, withData: packet.binary, completion: wrappedCompletion)
  271. }
  272. /// Call when you wish to tell the server that you've received the event for `ack`.
  273. ///
  274. /// **You shouldn't need to call this directly.** Instead use an `SocketAckEmitter` that comes in an event callback.
  275. ///
  276. /// - parameter ack: The ack number.
  277. /// - parameter with: The data for this ack.
  278. open func emitAck(_ ack: Int, with items: [Any]) {
  279. emit(items, ack: ack, binary: true, isAck: true)
  280. }
  281. /// Called when socket.io has acked one of our emits. Causes the corresponding ack callback to be called.
  282. ///
  283. /// - parameter ack: The number for this ack.
  284. /// - parameter data: The data sent back with this ack.
  285. @objc
  286. open func handleAck(_ ack: Int, data: [Any]) {
  287. guard status == .connected else { return }
  288. DefaultSocketLogger.Logger.log("Handling ack: \(ack) with data: \(data)", type: logType)
  289. ackHandlers.executeAck(ack, with: data)
  290. }
  291. /// Called on socket.io specific events.
  292. ///
  293. /// - parameter event: The `SocketClientEvent`.
  294. /// - parameter data: The data for this event.
  295. open func handleClientEvent(_ event: SocketClientEvent, data: [Any]) {
  296. handleEvent(event.rawValue, data: data, isInternalMessage: true)
  297. }
  298. /// Called when we get an event from socket.io.
  299. ///
  300. /// - parameter event: The name of the event.
  301. /// - parameter data: The data that was sent with this event.
  302. /// - parameter isInternalMessage: Whether this event was sent internally. If `true` it is always sent to handlers.
  303. /// - parameter ack: If > 0 then this event expects to get an ack back from the client.
  304. @objc
  305. open func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int = -1) {
  306. guard status == .connected || isInternalMessage else { return }
  307. DefaultSocketLogger.Logger.log("Handling event: \(event) with data: \(data)", type: logType)
  308. anyHandler?(SocketAnyEvent(event: event, items: data))
  309. for handler in handlers where handler.event == event {
  310. handler.executeCallback(with: data, withAck: ack, withSocket: self)
  311. }
  312. }
  313. /// Causes a client to handle a socket.io packet. The namespace for the packet must match the namespace of the
  314. /// socket.
  315. ///
  316. /// - parameter packet: The packet to handle.
  317. open func handlePacket(_ packet: SocketPacket) {
  318. guard packet.nsp == nsp else { return }
  319. switch packet.type {
  320. case .event, .binaryEvent:
  321. handleEvent(packet.event, data: packet.args, isInternalMessage: false, withAck: packet.id)
  322. case .ack, .binaryAck:
  323. handleAck(packet.id, data: packet.data)
  324. case .connect:
  325. didConnect(toNamespace: nsp)
  326. case .disconnect:
  327. didDisconnect(reason: "Got Disconnect")
  328. case .error:
  329. handleEvent("error", data: packet.data, isInternalMessage: true, withAck: packet.id)
  330. }
  331. }
  332. /// Call when you wish to leave a namespace and disconnect this socket.
  333. @objc
  334. open func leaveNamespace() {
  335. manager?.disconnectSocket(self)
  336. }
  337. /// Joins `nsp`.
  338. @objc
  339. open func joinNamespace() {
  340. DefaultSocketLogger.Logger.log("Joining namespace \(nsp)", type: logType)
  341. manager?.connectSocket(self)
  342. }
  343. /// Removes handler(s) for a client event.
  344. ///
  345. /// If you wish to remove a client event handler, call the `off(id:)` with the UUID received from its `on` call.
  346. ///
  347. /// - parameter clientEvent: The event to remove handlers for.
  348. open func off(clientEvent event: SocketClientEvent) {
  349. off(event.rawValue)
  350. }
  351. /// Removes handler(s) based on an event name.
  352. ///
  353. /// If you wish to remove a specific event, call the `off(id:)` with the UUID received from its `on` call.
  354. ///
  355. /// - parameter event: The event to remove handlers for.
  356. @objc
  357. open func off(_ event: String) {
  358. DefaultSocketLogger.Logger.log("Removing handler for event: \(event)", type: logType)
  359. handlers = handlers.filter({ $0.event != event })
  360. }
  361. /// Removes a handler with the specified UUID gotten from an `on` or `once`
  362. ///
  363. /// If you want to remove all events for an event, call the off `off(_:)` method with the event name.
  364. ///
  365. /// - parameter id: The UUID of the handler you wish to remove.
  366. @objc
  367. open func off(id: UUID) {
  368. DefaultSocketLogger.Logger.log("Removing handler with id: \(id)", type: logType)
  369. handlers = handlers.filter({ $0.id != id })
  370. }
  371. /// Adds a handler for an event.
  372. ///
  373. /// - parameter event: The event name for this handler.
  374. /// - parameter callback: The callback that will execute when this event is received.
  375. /// - returns: A unique id for the handler that can be used to remove it.
  376. @objc
  377. @discardableResult
  378. open func on(_ event: String, callback: @escaping NormalCallback) -> UUID {
  379. DefaultSocketLogger.Logger.log("Adding handler for event: \(event)", type: logType)
  380. let handler = SocketEventHandler(event: event, id: UUID(), callback: callback)
  381. handlers.append(handler)
  382. return handler.id
  383. }
  384. /// Adds a handler for a client event.
  385. ///
  386. /// Example:
  387. ///
  388. /// ```swift
  389. /// socket.on(clientEvent: .connect) {data, ack in
  390. /// ...
  391. /// }
  392. /// ```
  393. ///
  394. /// - parameter event: The event for this handler.
  395. /// - parameter callback: The callback that will execute when this event is received.
  396. /// - returns: A unique id for the handler that can be used to remove it.
  397. @discardableResult
  398. open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID {
  399. return on(event.rawValue, callback: callback)
  400. }
  401. /// Adds a single-use handler for a client event.
  402. ///
  403. /// - parameter clientEvent: The event for this handler.
  404. /// - parameter callback: The callback that will execute when this event is received.
  405. /// - returns: A unique id for the handler that can be used to remove it.
  406. @discardableResult
  407. open func once(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID {
  408. return once(event.rawValue, callback: callback)
  409. }
  410. /// Adds a single-use handler for an event.
  411. ///
  412. /// - parameter event: The event name for this handler.
  413. /// - parameter callback: The callback that will execute when this event is received.
  414. /// - returns: A unique id for the handler that can be used to remove it.
  415. @objc
  416. @discardableResult
  417. open func once(_ event: String, callback: @escaping NormalCallback) -> UUID {
  418. DefaultSocketLogger.Logger.log("Adding once handler for event: \(event)", type: logType)
  419. let id = UUID()
  420. let handler = SocketEventHandler(event: event, id: id) {[weak self] data, ack in
  421. guard let this = self else { return }
  422. this.off(id: id)
  423. callback(data, ack)
  424. }
  425. handlers.append(handler)
  426. return handler.id
  427. }
  428. /// Adds a handler that will be called on every event.
  429. ///
  430. /// - parameter handler: The callback that will execute whenever an event is received.
  431. @objc
  432. open func onAny(_ handler: @escaping (SocketAnyEvent) -> ()) {
  433. anyHandler = handler
  434. }
  435. /// Tries to reconnect to the server.
  436. @objc
  437. @available(*, unavailable, message: "Call the manager's reconnect method")
  438. open func reconnect() { }
  439. /// Removes all handlers.
  440. ///
  441. /// Can be used after disconnecting to break any potential remaining retain cycles.
  442. @objc
  443. open func removeAllHandlers() {
  444. handlers.removeAll(keepingCapacity: false)
  445. }
  446. /// Puts the socket back into the connecting state.
  447. /// Called when the manager detects a broken connection, or when a manual reconnect is triggered.
  448. ///
  449. /// - parameter reason: The reason this socket is reconnecting.
  450. @objc
  451. open func setReconnecting(reason: String) {
  452. status = .connecting
  453. handleClientEvent(.reconnect, data: [reason])
  454. }
  455. // Test properties
  456. var testHandlers: [SocketEventHandler] {
  457. return handlers
  458. }
  459. func setTestable() {
  460. status = .connected
  461. }
  462. func setTestStatus(_ status: SocketIOStatus) {
  463. self.status = status
  464. }
  465. func emitTest(event: String, _ data: Any...) {
  466. emit([event] + data)
  467. }
  468. }