Skip to content

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

  1. Você deve definir canais para receber conexões WebSocket de entrada e o mesmo é feito dentro do arquivo app/Ws/socket.js.
  2. Todas as conexões de entrada podem ser autenticadas usando o middleware auth.
  3. Todas as ações de web sockets suportam geradores ES2015. Por exemplo:
    js
    Ws.channel('/chat', function (socket) {
      socket.on('message', function * (payload) {
      })
    })
  4. 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.

bash
npm i --save adonis-websocket

Em seguida, precisamos registrar o provedor e configurar o alias dentro do arquivo bootstrap/app.js.

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.

bash
mkdir -p app/Ws/Controllers
touch app/Ws/socket.js
touch app/Ws/kernel.js
  1. 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
    })
  2. 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.

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.

js
// 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.

js
Ws.channel('chat', 'ChatController')

Agora os controladores podem escutar novos eventos apenas criando métodos apropriados nele.

js
// 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.

js
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 eventoMétodo do controlador
messageonMessage
new:useronNewUser
user:leftonUserLeft

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

js
// app/Ws/socket.js

const Ws = use('Ws')

Ws
.channel('chat', 'ChatController')
.middleware('auth')
js
// 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

js
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

js
this.socket.inRoom('lobby').emit('message', 'Hello world')

Cliente

js
client.on('message', function (room, message) {
})

Saindo de uma sala

Para sair de uma sala, o cliente pode chamar o método leaveRoom.

Servidor

js
// 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

js
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.

js
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.

js
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.

js
socket.on('greet', function (greeting) {

})

once(event, callback)

Ouvir um evento apenas uma vez.

js
socket.once('greet', function (greeting) {

})

emit(event, ...properties)

Emitir um evento.

js
socket.emit('greet', 'Hello world')

toEveryone()

Emitir uma mensagem para todos, incluindo o próprio socket de origem.

js
socket.toEveryone().emit('greet', 'Hello world')

toMe()

Emitir uma mensagem apenas para o socket de origem.

js
socket.toMe().emit('greet', 'Hello world')

exceptMe()

Emitir uma mensagem para todos, exceto o socket de origem.

js
socket.exceptMe().emit('user:join', 'User joined!')

to(ids)

Emitir uma mensagem apenas para IDs de socket específicos.

js
socket.to([]).emit('greet', 'Hello world')

inRoom(room)

Emitir uma mensagem para uma sala específica.

js
socket.inRoom('lobby').emit('greet', 'Hello world')

inRooms(rooms)

Emitir uma mensagem para várias salas.

js
socket.inRoom(['lobby', 'watercooler']).emit('greet', 'Hello world')

disconnect

Desconectar um socket de receber/enviar mensagens.

js
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.

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.

js
const chatChannel = Ws.channel('chat')
chatChannel.emit('message', 'Hello world')

inRoom(room)

Emite uma mensagem para uma sala fornecida.

js
const chatChannel = Ws.channel('chat')
chatChannel.inRoom('lobby').emit('message', 'Hello world')

inRooms(rooms)

Emite uma mensagem para todas as salas fornecidas.

js
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.

js
const chatChannel = Ws.channel('chat')
chatChannel.to([]).emit('greet', 'Hello world')

get(socketId)

Obtenha a instância do socket usando o id do socket.

js
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.

bash
npm i --save adonis-websocket-client
js
const ws = require('adonis-websocket-client')
const io = ws('http://localhost:3333', {})

Uso do AMD

Primeiro, instale o pacote do bower.

bash
bower i --save adonis-websocket-client
js
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.

html
<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.

js
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.

js
client.emit('message', 'Hello world')

on(event, callback)

Ouvir um evento.

js
client.on('message', function (message) {
})

once(event, callback)

Ouvir um evento apenas uma vez.

js
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.

js
client.joinRoom('lobby', {}, function (error, joined) {
})

leaveRoom(room, payload, callback)

Sai de uma sala.

js
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.

js
client
  .withBasicAuth('foo', 'secret')
  .connect(function () {
  })

withJwt(token)

Conecte-se ao canal passando o token JWT a ser usado para autenticação.

js
client
  .withJwt('token')
  .connect(function () {
  })

withApiKey(token)

Conecte-se ao canal passando o token API pessoal a ser usado para autenticação.

js
client
  .withApiKey('personal_token')
  .connect(function () {
  })