diff --git a/README.md b/README.md index c472316..2e7299b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ import SocketIO let socket = SocketIOClient(socketURL: URL(string: "http://localhost:8080")!, config: [.log(true), .forcePolling(true)]) -socket.on("connect") {data, ack in +socket.on(clientEvent: .connect) {data, ack in print("socket connected") } diff --git a/SocketIO-MacTests/SocketSideEffectTest.swift b/SocketIO-MacTests/SocketSideEffectTest.swift index e277aef..8f24c63 100644 --- a/SocketIO-MacTests/SocketSideEffectTest.swift +++ b/SocketIO-MacTests/SocketSideEffectTest.swift @@ -177,6 +177,50 @@ class SocketSideEffectTest: XCTestCase { waitForExpectations(timeout: 0.2) } + func testOnClientEvent() { + let expect = expectation(description: "The client should call client event handlers") + let event = SocketClientEvent.disconnect + let closeReason = "testing" + + socket.on(clientEvent: event) {data, ack in + guard let reason = data[0] as? String else { + XCTFail("Client should pass data for client events") + + return + } + + XCTAssertEqual(closeReason, reason, "The data should be what was sent to handleClientEvent") + + expect.fulfill() + } + + socket.handleClientEvent(event, data: [closeReason]) + + waitForExpectations(timeout: 0.2) + } + + func testClientEventsAreBackwardsCompatible() { + let expect = expectation(description: "The client should call old style client event handlers") + let event = SocketClientEvent.disconnect + let closeReason = "testing" + + socket.on("disconnect") {data, ack in + guard let reason = data[0] as? String else { + XCTFail("Client should pass data for client events") + + return + } + + XCTAssertEqual(closeReason, reason, "The data should be what was sent to handleClientEvent") + + expect.fulfill() + } + + socket.handleClientEvent(event, data: [closeReason]) + + waitForExpectations(timeout: 0.2) + } + let data = "test".data(using: String.Encoding.utf8)! let data2 = "test2".data(using: String.Encoding.utf8)! private var socket: SocketIOClient! diff --git a/Source/SocketIOClient.swift b/Source/SocketIOClient.swift index 384fbfd..258c37b 100644 --- a/Source/SocketIOClient.swift +++ b/Source/SocketIOClient.swift @@ -48,7 +48,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So break } - handleEvent("statusChange", data: [status], isInternalMessage: true) + handleClientEvent(.statusChange, data: [status]) } } @@ -204,11 +204,10 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So func didConnect() { DefaultSocketLogger.Logger.log("Socket connected", type: logType) + status = .connected - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - handleEvent("connect", data: [], isInternalMessage: false) + handleClientEvent(.connect, data: []) } func didDisconnect(reason: String) { @@ -221,7 +220,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So // Make sure the engine is actually dead. engine?.disconnect(reason: reason) - handleEvent("disconnect", data: [reason], isInternalMessage: true) + handleClientEvent(.disconnect, data: [reason]) } /// Disconnects the socket. @@ -249,7 +248,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So /// - parameter with: The items to send with this event. May be left out. open func emit(_ event: String, with items: [Any]) { guard status == .connected else { - handleEvent("error", data: ["Tried emitting \(event) when not connected"], isInternalMessage: true) + handleClientEvent(.error, data: ["Tried emitting \(event) when not connected"]) return } @@ -302,7 +301,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So func _emit(_ data: [Any], ack: Int? = nil) { guard status == .connected else { - handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true) + handleClientEvent(.error, data: ["Tried emitting when not connected"]) return } @@ -362,7 +361,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So private func _engineDidError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: logType, args: reason) - handleEvent("error", data: [reason], isInternalMessage: true) + handleClientEvent(.error, data: [reason]) } /// Called when the engine opens. @@ -399,6 +398,10 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So } } + func handleClientEvent(_ event: SocketClientEvent, data: [Any]) { + handleEvent(event.rawValue, data: data, isInternalMessage: true) + } + /// Leaves nsp and goes back to the default namespace. open func leaveNamespace() { if nsp != "/" { @@ -458,6 +461,30 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So return handler.id } + /// Adds a handler for a client event. + /// + /// Example: + /// + /// ```swift + /// socket.on(clientEvent: .connect) {data, ack in + /// ... + /// } + /// ``` + /// + /// - parameter event: The event for this handler. + /// - parameter callback: The callback that will execute when this event is received. + /// - returns: A unique id for the handler that can be used to remove it. + @discardableResult + open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID { + DefaultSocketLogger.Logger.log("Adding handler for event: %@", type: logType, args: event) + + let handler = SocketEventHandler(event: event.rawValue, id: UUID(), callback: callback) + handlers.append(handler) + + return handler.id + } + + /// Adds a single-use handler for an event. /// /// - parameter event: The event name for this handler. @@ -522,7 +549,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So guard reconnecting else { return } DefaultSocketLogger.Logger.log("Starting reconnect", type: logType) - handleEvent("reconnect", data: [reason], isInternalMessage: true) + handleClientEvent(.reconnect, data: [reason]) _tryReconnect() } @@ -535,7 +562,7 @@ open class SocketIOClient : NSObject, SocketIOClientSpec, SocketEngineClient, So } DefaultSocketLogger.Logger.log("Trying to reconnect", type: logType) - handleEvent("reconnectAttempt", data: [(reconnectAttempts - currentReconnectAttempt)], isInternalMessage: true) + handleClientEvent(.reconnectAttempt, data: [(reconnectAttempts - currentReconnectAttempt)]) currentReconnectAttempt += 1 connect() diff --git a/Source/SocketIOClientSpec.swift b/Source/SocketIOClientSpec.swift index 539c4ec..b4b99a1 100644 --- a/Source/SocketIOClientSpec.swift +++ b/Source/SocketIOClientSpec.swift @@ -34,6 +34,7 @@ protocol SocketIOClientSpec : class { func didError(reason: String) func handleAck(_ ack: Int, data: [Any]) func handleEvent(_ event: String, data: [Any], isInternalMessage: Bool, withAck ack: Int) + func handleClientEvent(_ event: SocketClientEvent, data: [Any]) func joinNamespace(_ namespace: String) } @@ -41,6 +42,27 @@ extension SocketIOClientSpec { func didError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: "SocketIOClient", args: reason) - handleEvent("error", data: [reason], isInternalMessage: true, withAck: -1) + handleClientEvent(.error, data: [reason]) } } + +/// The set of events that are generated by the client. +public enum SocketClientEvent : String { + /// Emitted when the client connects. This is also called on a successful reconnection. + case connect + + /// Called when the socket has disconnected and will not attempt to try to reconnect. + case disconnect + + /// Called when an error occurs. + case error + + /// Called when the client begins the reconnection process. + case reconnect + + /// Called each time the client tries to reconnect to the server. + case reconnectAttempt + + /// Called every time there is a change in the client's status. + case statusChange +}