Ws.channel('/chat', function (socket) {
socket.on('message', function * (payload) {
})
})
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
WebSocket Provider makes it super easy to build real-time applications in AdonisJs. It comes with out of the box support for authentication, channels and rooms management.
You are supposed to define channels to receive incoming WebSocket connections and same is done inside app/Ws/socket.js
file.
All incoming connections can be authenticated using the auth
middleware.
All web sockets actions supports ES2015 generators. For example:
Ws.channel('/chat', function (socket) {
socket.on('message', function * (payload) {
})
})
You can attach controllers to your channels, the same as with routes.
Ws.channel('/chat', 'ChatController')
Feel free to skip the setup process if you are using AdonisJs 3.2 or later. Since Web Sockets are pre-configured. |
npm i --save adonis-websocket
Next, we need to register the provider and setup alias inside bootstrap/app.js
file.
const providers = [
'adonis-websocket/providers/WsProvider'
]
const aliases = {
Ws: 'Adonis/Addons/Ws'
}
Next, we need to create required directories and files to organize our code.
mkdir -p app/Ws/Controllers
touch app/Ws/socket.js
touch app/Ws/kernel.js
The socket.js
file is used to define websocket channels, you can think of it as the Http routes file.
const Ws = use('Ws')
Ws.channel('/chat', function (socket) {
// here you go
})
The kernel.js
file is used to define global and named middleware just like your Http middleware but for Websocket connections.
const Ws = use('Ws')
const globalMiddleware = [
'Adonis/Middleware/AuthInit'
]
const namedMiddleware = {
auth: 'Adonis/Middleware/Auth'
}
Ws.global(globalMiddleware)
Ws.named(namedMiddleware)
Finally, we need to load socket.js
and kernel.js
file when booting the Http server and same can be done inside bootstrap/http.js
file.
use(Helpers.makeNameSpace('Ws', 'kernel'))
use(Helpers.makeNameSpace('Ws', 'socket'))
Check out the following video showing how to exchange messages between client and the server.
Channels make it easier to distribute logic around exposing web socket endpoints. For each channel, you can follow different authentication process or bind different middleware to it.
Adonisjs makes use of multiplexing instead of creating a different connection for each channel. |
'use strict'
const Ws = use('Ws')
Ws.channel('chat', function (socket, request, presence) {
socket.on('news', function (message) {
})
})
The above closure will be executed every time a new socket will join the chat channel and receives following.
Apart from closures, you can also bind controllers to channels. All controllers are stored inside app/Ws/Controllers
directory and can be referenced the same way as Route controllers.
Ws.channel('chat', 'ChatController')
Now controllers can listen for new events just by creating appropriate methods on it.
'use strict'
class ChatController {
constructor (socket) {
this.socket = socket
}
onMessage (message) {
// listening for message event
}
}
The onMessage
method will be invoked every time message event will be fired from the client. Also, you can make your listeners a generator method for doing async operations.
onMessage (message) {
}
// CAN BE
* onMessage (message) {
const savedMessage = yield Message.create({ body: message })
}
All events listeners must start with on
and the camel case representation of the event name. For example new:user
will invoke onNewUser
method on the controller.
Event Name | Controller Method |
---|---|
message |
onMessage |
new:user |
onNewUser |
user:left |
onUserLeft |
Rooms make it easier to build multi-room chat systems. For example, Slack has public rooms that anyone can join and leave, whereas private rooms need further authorization.
In the same manner, AdonisJs gives you hooks to authorize a socket before it can listen for events inside a room.
The joinRoom
method on the channel controller has invoked automatically every time a socket tries to join a room. You can make use of this method to authorize the join action or deny it by throwing an exception.
const Ws = use('Ws')
Ws
.channel('chat', 'ChatController')
.middleware('auth')
'use strict'
class ChatController {
constructor (socket) {
this.socket = socket
}
* joinRoom (room) {
const user = this.socket.currentUser
// throw error to deny a socket from joining room
}
}
const io = ws('')
const client = io.channel('chat').connect()
client.joinRoom('lobby', {}, function (error, joined) {
// status
})
Once a socket has joined a room, it can listen for messages.
this.socket.inRoom('lobby').emit('message', 'Hello world')
client.on('message', function (room, message) {
})
In order to leave a room, the client can call leaveRoom
method.
'use strict'
class ChatController {
constructor (socket) {
this.socket = socket
}
* leaveRoom (room) {
// Do cleanup if required
}
* joinRoom (room) {
const user = this.socket.currentUser
// throw error to deny a socket from joining room
}
}
const io = ws('')
const client = io.channel('chat').connect()
client.leaveRoom('lobby', {}, function (error, left) {
// status
})
The presence feature allows you to track sockets for a given user. It is helpful in showing the list of online users and number of devices they are online from. Also when a user logouts, you can disconnect all of their related sockets to make sure they do not receive any realtime messages.
Checkout this video to understand the presence in-depth.
Below is the list of presence methods.
The track
method allows you to track a socket for a given user using their userId. Optionally you can pass meta data too.
class ChatController {
constructor (socket, request, presence) {
presence.track(socket, socket.currentUser.id, {
device: 'chrome'
})
}
}
Pull a list of sockets from the presence list for a given user. Pulled sockets will not be tracked anymore.
const Ws = use('Ws')
const chatChannel = Ws.channel('chat')
const chromeOnlySockets = chatChannel.presence.pull(userId, function (payload) {
return payload.meta.device === 'chrome'
})
// disconnect user sockets from chrome
chromeOnlySockets.forEach((payload) => {
payload.socket.disconnect()
})
Below is the list of methods you can call from the socket instance.
Emit a message to everyone including the originating socket itself.
socket.toEveryone().emit('greet', 'Hello world')
Emit a message to everyone except the originating socket.
socket.exceptMe().emit('user:join', 'User joined!')
Emit a message to multiple rooms.
socket.inRoom(['lobby', 'watercooler']).emit('greet', 'Hello world')
Below is the list of methods can be used on the channel instance.
Apply an array of middleware on a given channel. Make sure to define middleware inside app/Ws/kernel.js
file.
Ws
.channel('chat')
.middleware('auth')
// OR
Ws
.channel('chat')
.middleware('auth:jwt')
Emit a message to all the sockets connected to a given channel.
const chatChannel = Ws.channel('chat')
chatChannel.emit('message', 'Hello world')
Emit a message a given room.
const chatChannel = Ws.channel('chat')
chatChannel.inRoom('lobby').emit('message', 'Hello world')
Emit a message to all given rooms.
const chatChannel = Ws.channel('chat')
chatChannel.inRooms(['lobby', 'watercooler']).emit('message', 'Hello world')
Emit a message to specific socket ids only.
const chatChannel = Ws.channel('chat')
chatChannel.to([]).emit('greet', 'Hello world')
Get socket instance using the socket id.
const chatChannel = Ws.channel('chat')
const socket = chatChannel.get(socketId)
The client library to be used with browser-based web apps can be installed as Common Js module from npm, AMD module from bower or you can reference it from a CDN.
After installation, you can require the module just like any other npm module.
npm i --save adonis-websocket-client
const ws = require('adonis-websocket-client')
const io = ws('http://localhost:3333', {})
First, install the package from bower.
bower i --save adonis-websocket-client
requirejs(['adonis-websocket-client'], function (ws) {
const io = ws('http://localhost:3333', {})
})
The CDN script file will create a ws
global.
<script src="https://unpkg.com/adonis-websocket-client/dist/ws.min.js"></script>
<script>
const io = ws('http://localhost:3333', {})
</script>
Below is the list of methods you can call using the client SDK.
Connect to a given channel.
const client = io.channel('chat')
client.connect(function (error, connected) {
if (error) {
// do something
return
}
// all good
})
Notify server to join a room and send optional data object as payload.
client.joinRoom('lobby', {}, function (error, joined) {
})
Leave a room.
client.leaveRoom('lobby', {}, function (error, left) {
})
Connect to the channel by passing the username and password to be used for basic auth.
client
.withBasicAuth('foo', 'secret')
.connect(function () {
})
Connect to the channel by passing JWT token to be used for authentication.
client
.withJwt('token')
.connect(function () {
})
Connect to the channel by passing personal API token to be used for authentication.
client
.withApiKey('personal_token')
.connect(function () {
})