As we use .default WSID which represents Declarative observer we can handle incoming data like this
app.ws.build(.default).at("ws").middlewares(AuthMiddleware()).serve().onOpen { client in
print("client just connected \(client.id)")
}.onText { client, text in
print("client \(client.id) text: \(text)")
}
there are also available: onClose, onPing, onPong, onBinary, onByteBuffer handlers.
💡Set app.logger.logLevel = .info or app.logger.logLevel = .debug to see more info about connections
Classic observer
You should create new class which inherit from ClassicObserver
import WS
class MyClassicWebSocket: ClassicObserver {
override func on(open client: AnyClient) {}
override func on(close client: AnyClient) {}
override func on(text: String, client: AnyClient) {}
/// also you can override: `on(ping:)`, `on(pong:)`, `on(binary:)`, `on(byteBuffer:)`
}
e.g. you want to find all ws connections of the current user to send a message to all his devices
req.ws(.mywsid).broadcast.filter { client in
req.headers[.authorization].first == client.originalRequest.headers[.authorization].first
}.send(...)
Broadcast
You could reach broadcast obejct on app.ws.observer(.mywsid) or req.ws(.mywsid).broadcast or client.broadcast.
This object is a builder, so using it you should filter recipients like this client.broadcast.one(...).two(...).three(...).send()
Available methods
.encoder(Encoder) // set custom data encoder
.exclude([AnyClient]) // exclude provided clients from clients
.filter((AnyClient) -> Bool) // filter clients by closure result
.channels([String]) // filter clients by provided channels
.subscribe([String]) // subscribe filtered clients to channels
.unsubscribe([String]) // unsubscribe filtered clients from channels
.disconnect() // disconnect filtered clients
.send(...) // send message to filtered clients
.count // number of filtered clients
Channels
Subscribe
client.subscribe(to: ..., on: eventLoop) // will subscribe client to provided channels
To subscribe to news and updates call it like this client.subscribe(to: "news", "updates")
Unsubscribe
client.unsubscribe(from: ..., on: eventLoop) // will unsubscribe client from provided channels
List
client.channels // will return a list of client channels
Defaults
If you have only one observer in the app you can set it as default. It will give you ability to use it without providing its WSID all the time, so you will call just req.ws() instead of req.ws(.mywsid).
// configure.swift
app.ws.setDefault(.myBindable)
Also you can set custom encoder/decoder for all the observers
Receive & send websocket messages through convenient observers. Even multiple observers on different endpoints!
Built for Vapor4.
If you have great ideas of how to improve this package write me (@iMike#3049) in Vapor’s discord chat or just send pull request.
Install through Swift Package Manager
Edit your
Package.swiftHow it works ?
Declarative observer
WS lib have
.defaultWSID which representsDeclarativeObserver.You can start working with it this easy way
In this case it will start listening for websocket connections at
/, but you can change it before you call.serve()Ok now it is listening at
/wsAlso you can protect your websocket endpoint with middlewares, e.g. you can check auth before connection will be established.
Ok, looks good, but how to handle incoming data?
As we use
.defaultWSID which representsDeclarativeobserver we can handle incoming data like thisthere are also available:
onClose,onPing,onPong,onBinary,onByteBufferhandlers.Classic observer
You should create new class which inherit from
ClassicObserverand you must declare a WSID for it
so then start serving it
Bindable observer
This kind of observer designed to send and receive events in special format, e.g. in JSON:
or just
First of all declare any possible events in
EIDextension like thisThen create your custom bindable observer class
declare a WSID
then start serving it
How to send data
Data sending works through
Sendableprotocol, which have several methodsUsing methods listed above you could send messages to one or multiple clients.
To one client e.g. in
on(open:)oron(text:)To all clients
To clients in channels
To custom filtered clients
e.g. you want to find all ws connections of the current user to send a message to all his devices
Broadcast
You could reach
broadcastobejct onapp.ws.observer(.mywsid)orreq.ws(.mywsid).broadcastorclient.broadcast.This object is a builder, so using it you should filter recipients like this
client.broadcast.one(...).two(...).three(...).send()Available methods
Channels
Subscribe
To subscribe to
newsandupdatescall it like thisclient.subscribe(to: "news", "updates")Unsubscribe
List
Defaults
If you have only one observer in the app you can set it as default. It will give you ability to use it without providing its WSID all the time, so you will call just
req.ws()instead ofreq.ws(.mywsid).Also you can set custom encoder/decoder for all the observers
Client
As you may see in every handler you always have
clientobject. This object conforms toAnyClientprotocol which contains useful things insidevariables
id- UUIDoriginalRequest- originalRequesteventLoop- nextEventLoopapplication- pointer toApplicationchannels- an array of channels that client subscribed tologger- pointer toLoggerobserver- this client’s observersockets- original socket connection of the clientexchangeMode- client’s observer exchange modeconformance
Sendable- so you can use.send(...)Subscribable- so you can use.subscribe(...),.unsubscribe(...)Disconnectable- so you can call.disconnect()to disconnect that userOriginal request gives you ability to e.g. determine connected user:
How to implement automatic ping?
Please take a look at this gist example
How to connect from iOS, macOS, etc?
You could use pure
URLSessionwebsockets functionality since iOS13, or for example you could use my CodyFire lib or classic Starscream libHow to connect from Android?
Use any lib which support pure websockets protocol, e.g. not SocketIO cause it uses its own protocol.
Examples
There are no examples for Vapor 4 yet unfortunately.
Contacts
Please feel free to contact me in Vapor’s discord my nickname is
iMike#3049Contribution
Feel free to contribute!