Swiftpack.co - Package - MihaelIsaev/WS

Mihael Isaev

MIT License Swift 4.2 Twitter


Receive & send websocket messages through convenient controllers

🚧 This project is under active development and API's and ideology may be changed or renamed until v1.0.0 🚧

Install through Swift Package Manager

Edit your Package.swift

//add this repo to dependencies
.package(url: "https://github.com/MihaelIsaev/WS.git", from: "0.10.0")
//and don't forget about targets
//"WS"

Setup in configure.swift

import WS

let ws = WS(at: "ws", protectedBy: [someMiddleware1, someMiddleware2], delegate: SOME_CONTROLLER)
// ws.logger.level = .debug
services.register(ws, as: WebSocketServer.self)

Let's take a look at WS initializations params. First param is a path of endpoint where you'd like to listen for a websocket connection, in this example it is /ws, but you could provide any as you do it for enpoints in Vapor router. Second param is optional, it's an array of middlewares which are protecting your websocket endpoint. e.g. for protecting b bearer token

let tokenAuthMiddleware = User.tokenAuthMiddleware()
let guardAuthMiddleware = User.guardAuthMiddleware()

Third parameter is a controller object which will receive and handle all ws events like onOpen, onClose, onText, onBinary, onError.

⚠️ If you use middlewares please do not send any events to the server right in onOpen method. It is discussed in #1

Controllers

Pure controller (classic)

let pureController = WSPureController()
pureController.onOpen = { client in
    
}
pureController.onClose = {
    
}
pureController.onError = { client, error in
    
}
pureController.onBinary = { client, data in
    
}
pureController.onText = { client, text in
    
}

Custom controller

You could create some class which inherit from WSObserver and describe your own logic.

Bind controller

You could create custom controller wich is inherit from WSBindController

Let's take a look how it may look like

class WSController: WSBindController {
    override func onOpen(_ client: WSClient) {
        
    }
    override func onClose(_ client: WSClient) {
        
    }
}

Then you could bind to some events, but you should describe these events first.

e.g. we'd like to describe message event for our chat

struct MessagePayload: Codable {
    var fromUser: User.Public
    var text: String
}

extension WSEventIdentifier {
    static var message: WSEventIdentifier<MessagePayload> { return .init("message") }
    // Payload is optional, so if you don't want any payload you could provide `NoPayload` as a paylaod class.
}

Ok, then now we can bind to this event in our custom bind controller:

class WSController: WSBindController {
    override func onOpen(_ client: WSClient) {
        bind(.message, message)
    }
    override func onClose(_ client: WSClient) {
        
    }
}

extension WSController {
    func message(_ client: WSClient, _ payload: MessagePayload) { //or without payload if it's not needed!
        //handle incoming message here
    }
}

Easy, right?

Yeah, but you should know how its protocol works.

WSBindController listening for onText and onBinary, it expect that incoming data is json object in this format:

{
    "event": "some_event_name",
    "payload": {}
}

or just (cause payload is optional)

{
    "event": "some_event_name",
}

It is actual for both sending and receiving events.

WSClient

As you may see in every handler in both pure and bind controllers you always have client object. This object is WSClient class which contains a lot of useful things inside, like

variables

  • cid - UUID
  • req - original Request
  • eventLoop - EventLoop
  • channels - a list of channels of that user

methods

  • subscribe(to channels: [String]) - use it to subscribe client to some channels
  • unsubscribe(from channels: [String]) - use it to unsubscribe client from some channels
  • broadcast - a lot of broadcast variations, just use autocompletion to determine needed one

More than that, it is DatabaseConnectable, so you could run your queries like this

User.query(on: client).all()

Original req gives you ability to e.g. determine connected user:

let user = try client.req.requireAuthenticated(User.self)

Ok this is all about receiving websocket events.. what about sending?

Connected users

In WS object which is available from any container through .make method you could find a set of clients

let ws = try req.make(WS.self)
ws.clients //this is a set of all connected clients

Channels

Any client may be subscribed or unsubscribed from some channels.

Channel is WSChannel obejct with unique cid which is String.

You could subscribe/unsubscribe client to any channel by calling client,subscribe(to:) or client,unsubscribe(from:)

And you could broadcast to channels by calling ws.broadcast

Sending events

You could get an instance of WS anywhere where you have Container.

Broadcasting to some channel

e.g. in any request handler use broadcast method on ws object like this:

import WS

func sampleGetRequestHandler(_ req: Request) throws -> Future<HTTPStatus> {
    let user = try req.requireAuthenticated(User.self)
    let ws = try req.make(WS.self)
    let payload = MessagePayload(fromUser: User.Public(user), text: "Some text")
    return try ws.broadcast(asBinary: .message, payload, to: "some channel", on: req)
                 .transform(to: .ok)
}

Sending some event to concrete client

e.g. in any request handler find needed client from ws.clients set and then use emit or broadcast method

import WS

func sampleGetRequestHandler(_ req: Request) throws -> Future<HTTPStatus> {
    let user = try req.requireAuthenticated(User.self)
    let ws = try req.make(WS.self)
    let payload = MessagePayload(fromUser: User.Public(user), text: "Some text")
    return ws.clients.first!.emit("hello world", on: req).transform(to: .ok) //do not use force unwraiing in production!
}

How to connect from iOS, macOS, etc?

For example you could use Starscream lib

How to connect from Android?

Use any lib which support pure websockets protocol, e.g. not SocketIO cause it uses its own protocol.

Examples

Yeah, we have them!

AlexoChat project server and client

Contacts

Please feel free to contact me in Vapor's discord my nickname is iMike

Contribution

Feel free to contribute!

Github

link
Stars: 10
Help us keep the lights on

Dependencies

Used By

Total: 1