Skip to content

Network: Websocket based

Bruno Macabeus edited this page Jun 24, 2017 · 1 revision

The websocket system in the Stalkr tvOS client use the package Starscream.

Create a new websocket channel

In folder NetworkManager/WebSocket/WebSocketChannels, create a new Swift file. This file specific one new websocket channel to the client interact with the Stalkr User Service.

The examples code in this documentation is how create a simple chat room.

Events

We need specify the possibles events that this websocket channel could to send. To this, create a new enum. Each case in this enum is one possible event.

For example:

enum ChatWebSocketEvents {
    case newMessage(name: String?, text: String?)
    case someoneJoined(name: String?)
}

Each websocket channel need one delegate, to use in the controller.
In the Stalkr network layer, we already have a base delegate, called BaseWebSocketDelegate. The base delegate have a associatedtype EvetEnum. We need specific this associatedtype with the enum of our new websocket channel.

protocol ChatWebSocketDelegate: BaseWebSocketDelegate {
    func newMessage(socket: WebSocket, event: ChatWebSocketEvents)
}

Channel class

In this moment, we have a enum with the possibles events and a delegate. Now, we need create a class of our new websocket channel.

To create the class:

final class ChatWebSocketChannel<D: ChatWebSocketDelegate>: WebSocketChannel {

    let path = "chat" // path in the URL of Stalkr User Service
    let headers = ["client-type": "tvOS"] // header sent in each request
    var socket: WebSocket!
    
    typealias ChannelDelegate = D
    weak var delegate: D?

    func didReceiveMessage(_ response: ResponseText) {
        ...
    }

    func websocketDidReceiveData(socket: WebSocket, data: Data) {
        ...
    }
}

The function didReceiveMessage(_:) is called when the websocket receive a new text message. In this function you need write the code to call the delegate with the values received. For example:

func didReceiveMessage(_ response: ResponseText) {
    switch response.type {
    case "new_message":
        let messageName = response.data?["name"]?.stringValue
        let messageText = response.data?["text"]?.stringValue
        delegate?.newMessage(socket: socket, event: .newMessage(name: messageName, text: messageText))
        
    case "someone_joined":
        let userName = response.data?["name"]?.stringValue
        delegate?.newMessage(socket: socket, event: .someoneJoined(name: userName))
        
    default:
        // we don't expect that will receive a response type different from above
        delegate?.unexpectedMessage(socket: socket, unexpectedMessage: .typeUnknow(response))
    }
}

We call the function unexpectedMessage(socket:unexpectedMessage:) on delegate when receive a strange behavior. In this case, if we heave a event with type unexpected.

Now, we need to write the function websocketDidReceiveData(socket:data:). In this channel, we will never receive a binary data, then:

func websocketDidReceiveData(socket: WebSocket, data: Data) {
    // we don't expect that will receive a binnary data in this channel
    delegate?.unexpectedMessage(socket: socket, unexpectedMessage: .unexpectedBinnary(data))
}

Use a websocket in controller

Now, as we amazing websocket channel in network layer, we can use it in our controller. For it, the controller need subscriber the protocol ChatWebSocketDelegate:

class ChatViewController: UIViewController, ChatWebSocketDelegate { 
    
    func didConnect(socket: WebSocket) {
        ...
    }
    
    func didDisconnect(socket: WebSocket, error: NSError?) {
        ...
    }
    
    func newMessage(socket: WebSocket, event: ChatWebSocketEvents) {
        ...
    }
    
    func unexpectedMessage(socket: WebSocket, unexpectedMessage: WebSocketUnexpectedMessage) {
        ...
    }
}

Then, we need connect to the server

class ChatViewController: UIViewController, ChatWebSocketDelegate {
    
    var chatChannel: ChatWebSocketChannel<ChatViewController>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        chatChannel = ChatWebSocketChannel<ChatViewController>() // 1
        chatChannel!.delegate = self
        chatChannel!.socket.connect()
    }

    ...

In the liner numbered with 1 could be rewrite with:

let environment = Environment(name: "local", host: "ws://127.0.0.1:13254")
chatChannel = ChatWebSocketChannel<AuthenticationViewController>(environment: environment)

You can see the code about the default environment in the file NetworkManager/DefaultNetwork/DefaultNetwork.swift.

The BaseWebSocketDelegate (that the ChatWebSocketDelegate is heir) have four functions:

  • didConnect(socket:)
  • didDisconnect(socket:error:)
  • newMessage(socket:event:)
  • unexpectedMessage(socket:unexpectedMessage:)

One very simple example to implement this functions in the controller is:

func didConnect(socket: WebSocket) {
    print("chat connect!")
}
    
func didDisconnect(socket: WebSocket, error: NSError?) {
    print("WARNING: Chat's websocket is disconnected: \(error?.localizedDescription ?? "nil")")
}

func newMessage(socket: WebSocket, event: ChatWebSocketEvents) {
    switch event {
    case .newMessage(let name, let text):
        print("\(name) sent the message '\(text)'")
    case .someoneJoined(let name):
        print("user \(name) entered in room")
    }
}

func unexpectedMessage(socket: WebSocket, unexpectedMessage: WebSocketUnexpectedMessage) {
    print("WARNING: Unexpected message received in socket!")
}

Send a message to the server

TODO: Encapsulate the sending of messages.

If you want send a message to the server, you can use

socket.write(string: "foo")

You can see others ways in documentation of the Starscream.