WebSocket
O WebSocket Provider torna super fácil criar aplicativos em tempo real no AdonisJs. Ele vem com suporte pronto para uso para autenticação, canais e gerenciamento de salas.
Sobre o Ws Provider
- Você deve definir canais para receber conexões WebSocket de entrada e o mesmo é feito dentro do arquivo
app/Ws/socket.js
. - Todas as conexões de entrada podem ser autenticadas usando o middleware
auth
. - Todas as ações de web sockets suportam geradores ES2015. Por exemplo:js
Ws.channel('/chat', function (socket) { socket.on('message', function * (payload) { }) })
- Você pode anexar controladores aos seus canais, da mesma forma que com rotas.js
Ws.channel('/chat', 'ChatController')
Configuração
NOTA
Sinta-se à vontade para pular o processo de configuração se estiver usando o AdonisJs 3.2 ou posterior. Já que os Web Sockets são pré-configurados.
npm i --save adonis-websocket
Em seguida, precisamos registrar o provedor e configurar o alias dentro do arquivo bootstrap/app.js
.
// bootstrap/app.js
const providers = [
'adonis-websocket/providers/WsProvider'
]
const aliases = {
Ws: 'Adonis/Addons/Ws'
}
Em seguida, precisamos criar diretórios e arquivos necessários para organizar nosso código.
mkdir -p app/Ws/Controllers
touch app/Ws/socket.js
touch app/Ws/kernel.js
- O arquivo
socket.js
é usado para definir canais websocket, você pode pensar nele como o arquivo de rotas Http.js// app/Ws/socket.js const Ws = use('Ws') Ws.channel('/chat', function (socket) { // aqui você vai })
- O arquivo
kernel.js
é usado para definir middleware global e nomeado, assim como seu middleware Http, mas para conexões Websocket.js// app/Ws/kernel.js const Ws = use('Ws') const globalMiddleware = [ 'Adonis/Middleware/AuthInit' ] const namedMiddleware = { auth: 'Adonis/Middleware/Auth' } Ws.global(globalMiddleware) Ws.named(namedMiddleware)
Finalmente, precisamos carregar os arquivos socket.js
e kernel.js
ao inicializar o servidor Http e o mesmo pode ser feito dentro do arquivo bootstrap/http.js
.
// bootstrap/http.js
use(Helpers.makeNameSpace('Ws', 'kernel'))
use(Helpers.makeNameSpace('Ws', 'socket'))
Exemplo básico
Confira o vídeo a seguir mostrando como trocar mensagens entre o cliente e o servidor.
Canais
Os canais facilitam a distribuição da lógica em torno da exposição de endpoints de web socket. Para cada canal, você pode seguir um processo de autenticação diferente ou vincular um middleware diferente a ele.
DICA
Adonisjs faz uso de multiplexação em vez de criar uma conexão diferente para cada canal.
// app/Ws/socket.js
'use strict'
const Ws = use('Ws')
Ws.channel('chat', function (socket, request, presence) {
socket.on('news', function (message) {
})
})
O closure acima será executado toda vez que um novo socket se juntar ao canal chat e receber o seguinte.
- socket: instância de socket do usuário para emitir e ouvir eventos.
- request instanciado no momento do handshake.
- presença.
Controladores
Além dos fechamentos, você também pode vincular controladores a canais. Todos os controladores são armazenados dentro do diretório app/Ws/Controllers
e podem ser referenciados da mesma forma que os controladores de rota.
Ws.channel('chat', 'ChatController')
Agora os controladores podem escutar novos eventos apenas criando métodos apropriados nele.
// app/Ws/Controllers/ChatController.js
'use strict'
class ChatController {
constructor (socket) {
this.socket = socket
}
onMessage (message) {
// ouvindo evento de mensagem
}
}
O método onMessage
será invocado toda vez que o evento de mensagem for disparado do cliente. Além disso, você pode tornar seus ouvintes um método gerador para fazer operações assíncronas.
onMessage (message) {
}
// PODE SER
* onMessage (message) {
const savedMessage = yield Message.create({ body: message })
}
Todos os ouvintes de eventos devem começar com on
e a representação camel case do nome do evento. Por exemplo, new:user
invocará o método onNewUser
no controlador.
Nome do evento | Método do controlador |
---|---|
message | onMessage |
new:user | onNewUser |
user:left | onUserLeft |
Salas
As salas facilitam a criação de sistemas de bate-papo multi-salas. Por exemplo, o Slack tem salas públicas nas quais qualquer um pode entrar e sair, enquanto salas privadas precisam de autorização adicional.
Da mesma forma, o AdonisJs fornece ganchos para autorizar um soquete antes que ele possa escutar eventos dentro de uma sala.
Entrando em uma sala
O método joinRoom
no controlador de canal é invocado automaticamente toda vez que um soquete tenta entrar em uma sala. Você pode usar esse método para autorizar a ação de entrada ou negá-la lançando uma exceção.
Servidor
// app/Ws/socket.js
const Ws = use('Ws')
Ws
.channel('chat', 'ChatController')
.middleware('auth')
// app/Ws/Controllers/ChatController.js
'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
}
}
Cliente
const io = ws('')
const client = io.channel('chat').connect()
client.joinRoom('lobby', {}, function (error, joined) {
// status
})
Emitindo mensagens para uma sala
Depois que um soquete entra em uma sala, ele pode escutar mensagens.
Servidor
this.socket.inRoom('lobby').emit('message', 'Hello world')
Cliente
client.on('message', function (room, message) {
})
Saindo de uma sala
Para sair de uma sala, o cliente pode chamar o método leaveRoom
.
Servidor
// app/Ws/Controllers/ChatController.js
'use strict'
class ChatController {
constructor (socket) {
this.socket = socket
}
* leaveRoom (room) {
// Faça a limpeza se necessário
}
* joinRoom (room) {
const user = this.socket.currentUser
// lançar erro para negar um socket de entrar na sala
}
}
Cliente
const io = ws('')
const client = io.channel('chat').connect()
client.leaveRoom('lobby', {}, function (error, left) {
// status
})
Presença
O recurso de presença permite que você rastreie soquetes para um determinado usuário. É útil para mostrar a lista de usuários online e o número de dispositivos em que eles estão online. Além disso, quando um usuário faz logout, você pode desconectar todos os soquetes relacionados para garantir que ele não receba nenhuma mensagem em tempo real.
Confira este vídeo para entender a presença em profundidade.
Métodos de presença
Abaixo está a lista de métodos de presença.
track(socket, userId, [meta])
O método track
permite que você rastreie um socket para um determinado usuário usando seu userId. Opcionalmente, você pode passar metadados também.
class ChatController {
constructor (socket, request, presence) {
presence.track(socket, socket.currentUser.id, {
device: 'chrome'
})
}
}
pull(userId, callback)
Puxe uma lista de sockets da lista de presença para um determinado usuário. Sockets extraídos não serão mais rastreados.
const Ws = use('Ws')
const chatChannel = Ws.channel('chat')
const chromeOnlySockets = chatChannel.presence.pull(userId, function (payload) {
return payload.meta.device === 'chrome'
})
// desconectar sockets de usuário do chrome
chromeOnlySockets.forEach((payload) => {
payload.socket.disconnect()
})
Métodos de Socket
Abaixo está a lista de métodos que você pode chamar da instância do socket.
on(event, callback)
Ouvir um evento.
socket.on('greet', function (greeting) {
})
once(event, callback)
Ouvir um evento apenas uma vez.
socket.once('greet', function (greeting) {
})
emit(event, ...properties)
Emitir um evento.
socket.emit('greet', 'Hello world')
toEveryone()
Emitir uma mensagem para todos, incluindo o próprio socket de origem.
socket.toEveryone().emit('greet', 'Hello world')
toMe()
Emitir uma mensagem apenas para o socket de origem.
socket.toMe().emit('greet', 'Hello world')
exceptMe()
Emitir uma mensagem para todos, exceto o socket de origem.
socket.exceptMe().emit('user:join', 'User joined!')
to(ids)
Emitir uma mensagem apenas para IDs de socket específicos.
socket.to([]).emit('greet', 'Hello world')
inRoom(room)
Emitir uma mensagem para uma sala específica.
socket.inRoom('lobby').emit('greet', 'Hello world')
inRooms(rooms)
Emitir uma mensagem para várias salas.
socket.inRoom(['lobby', 'watercooler']).emit('greet', 'Hello world')
disconnect
Desconectar um socket de receber/enviar mensagens.
socket.disconnect()
Métodos de canal
Abaixo está a lista de métodos que podem ser usados na instância do canal.
middleware(...middleware)
Aplica uma matriz de middleware em um canal fornecido. Certifique-se de definir o middleware dentro do arquivo app/Ws/kernel.js
.
Ws
.channel('chat')
.middleware('auth')
// OU
Ws
.channel('chat')
.middleware('auth:jwt')
emit(event, ...properties)
Emite uma mensagem para todos os sockets conectados a um canal fornecido.
const chatChannel = Ws.channel('chat')
chatChannel.emit('message', 'Hello world')
inRoom(room)
Emite uma mensagem para uma sala fornecida.
const chatChannel = Ws.channel('chat')
chatChannel.inRoom('lobby').emit('message', 'Hello world')
inRooms(rooms)
Emite uma mensagem para todas as salas fornecidas.
const chatChannel = Ws.channel('chat')
chatChannel.inRooms(['lobby', 'watercooler']).emit('message', 'Hello world')
to(ids)
Emite uma mensagem apenas para IDs de sockets específicos.
const chatChannel = Ws.channel('chat')
chatChannel.to([]).emit('greet', 'Hello world')
get(socketId)
Obtenha a instância do socket usando o id do socket.
const chatChannel = Ws.channel('chat')
const socket = chatChannel.get(socketId)
Cliente WebSocket
A biblioteca cliente a ser usada com aplicativos da web baseados em navegador pode ser instalada como módulo Common Js do npm, módulo AMD do bower ou você pode referenciá-la de um CDN.
Uso do CommonJs
Após a instalação, você pode exigir o módulo como qualquer outro módulo npm.
npm i --save adonis-websocket-client
const ws = require('adonis-websocket-client')
const io = ws('http://localhost:3333', {})
Uso do AMD
Primeiro, instale o pacote do bower.
bower i --save adonis-websocket-client
requirejs(['adonis-websocket-client'], function (ws) {
const io = ws('http://localhost:3333', {})
})
Uso do CDN
O arquivo de script do CDN criará um ws
global.
<script src="https://unpkg.com/adonis-websocket-client/dist/ws.min.js"></script>
<script>
const io = ws('http://localhost:3333', {})
</script>
Métodos do canal do cliente
Abaixo está a lista de métodos que você pode chamar usando o SDK do cliente.
connect(callback)
Conecte-se a um determinado canal.
const client = io.channel('chat')
client.connect(function (error, connected) {
if (error) {
// faça alguma coisa
return
}
// tudo certo
})
emit(event, ...properties)
Emitir um evento.
client.emit('message', 'Hello world')
on(event, callback)
Ouvir um evento.
client.on('message', function (message) {
})
once(event, callback)
Ouvir um evento apenas uma vez.
client.once('message', function (message) {
})
joinRoom(room, payload, callback)
Notifica o servidor para entrar em uma sala e envia o objeto de dados opcional como payload.
client.joinRoom('lobby', {}, function (error, joined) {
})
leaveRoom(room, payload, callback)
Sai de uma sala.
client.leaveRoom('lobby', {}, function (error, left) {
})
withBasicAuth(username, password)
Conecte-se ao canal passando o nome de usuário e a senha a serem usados para autenticação básica.
client
.withBasicAuth('foo', 'secret')
.connect(function () {
})
withJwt(token)
Conecte-se ao canal passando o token JWT a ser usado para autenticação.
client
.withJwt('token')
.connect(function () {
})
withApiKey(token)
Conecte-se ao canal passando o token API pessoal a ser usado para autenticação.
client
.withApiKey('personal_token')
.connect(function () {
})