Usando o cabo de ação para mensagens privadas e indicadores de presença no aplicativo React / Rails

Alberto Carreras Blocked Desbloquear Seguir Seguindo 10 de janeiro

Este artigo baseia-se no excelente artigo de Dakota Lillie sobre como implementar o Action Cable com o React.

Mensagens ao vivo privadas são um recurso interessante para qualquer aplicativo. Adicionar um indicador visual que avisa quando outros usuários estão conectados no momento também pode ser um ótimo recurso para o seu projeto. No entanto, esses recursos exigem que seu cliente receba notificações sem enviar solicitações HTTP; portanto, deve haver uma conexão permanente entre seu cliente e o servidor. Aqui estão os Websockets que vieram para o resgate – para mais informações sobre Websockets, leia o artigo acima mencionado.

Em um dos meus primeiros projetos – SUPP , um aplicativo da web para conectar usuários com base em proximidade e interesses comuns – a criação de uma mensagem privada ao vivo era indispensável para o MVP e também queria adicionar indicadores ao vivo informando quando um usuário estava conectado ao aplicativo para facilitar comunicações imediatas.

Se esta é sua primeira abordagem ao Websockets, eu recomendo parar aqui e ler a versão de Dakota Lillie. artigo sobre como para configurar o infra-estrutura WebSocket básica para um chat aberto no Rails. Você também pode ler sobre a documentação do Action Cable.

Na postagem seguinte, criaremos mais documentação sobre como usar com êxito o Action Cable para mensagens privadas e indicadores de presença nos aplicativos React (com React-Redux) e Rails. O Action Cable é a implementação nativa incorporada do WebSockets no Rails. Para facilitar a implementação da funcionalidade do Cabo de Ação em nosso front-end React, usaremos o módulo Provedor de Ação de Reação .

Discutiremos soluções do Action Cable para criar salas de bate-papo privadas entre usuários e indicadores de presença. Por fim, proporemos um modo estruturado de implementar o Action Cable em seu aplicativo React usando “componentes de cabo”.

1. Cabo de ação e usuários autenticados

A primeira consideração a ter ao implementar o Action Cable in Rails é a solução de autenticação que usaremos. Na documentação, a seção 3.1.1 Configuração da conexão detalha como criar uma conexão WebSocket e identificar o usuário que está solicitando uma conexão.

 # app / channels / application_cable / connection.rb 
módulo ApplicationCable
conexão de classe <ActionCable :: Connection :: Base
identify_by: current_user

def conectar
self.current_user = find_verified_user
fim

privado
def find_verified_user
if verified_user = User.find_by (id: cookies.encrypted [: user_id])
verified_user
outro
reject_unauthorized_connection
fim
fim
fim
fim

Esse current_user é necessário quando você deseja que cada usuário stream_from canais privados em vez de canais abertos / públicos, como conversation_channel ou chat_#{params[:room]} . A seção 6.2 Exemplo 2: Recebendo Novas Notificações na Web na documentação mostra um exemplo de canal privado que somente o usuário conectado pode transmitir ou ouvir.

 # app / channels / web_notifications_channel.rb 
classe WebNotificationsChannel <ApplicationCable :: Channel
def subscrito
stream_for current_user
fim
fim

Muitos usuários podem se inscrever em qualquer canal público (salas de bate-papo) e transmitir a partir deles. Agora, a assinatura de canais privados para cada usuário autenticado permite que novas conversas e mensagens possam ser transmitidas para e, portanto, recebidas apenas pelo próprio usuário autenticado.

No entanto, neste ponto, você pode estar se perguntando como find_verified_user usando cookies. Quando configurei meu backend do Rails, implementei um sistema de autenticação usando tokens JWT usados em vez de cookies. A seção 8.3 Notas da documentação diz:

O servidor WebSocket não tem acesso à sessão, mas tem acesso aos cookies. Isso pode ser usado quando você precisa lidar com a autenticação. Você pode ver uma maneira de fazer isso com o Devise neste artigo .

Uma ótima solução é usar cookies somente HTTP para passar o token JWT entre o cliente e o servidor. Siga a seguinte implementação de JWT-tokens no Rails + React (postado por Sophie DeBenedetto).

Se você deseja implementar Devise bem -Infelizmente, o link artigo é broken poderá encontrar diversos tutoriais como este um . O Devise , como sua documentação explica, é uma solução de autenticação flexível para o Rails baseada no Warden, que é uma estrutura geral de autenticação do Rack. Isso significa que sob o capô Devise usa Warden para autenticação. O Devise tenta obter o usuário atualmente logado usando env['warden'].user reject_unauthorized_connection e, se não for encontrado, retorna reject_unauthorized_connection que proíbe a difusão.

Portanto, o connection.rb usa o diretor assim:

 # app / channels / application_cable / connection.rb 
módulo ApplicationCable
conexão de classe <ActionCable :: Connection :: Base
identify_by: current_user

def conectar
self.current_user = find_verified_user
fim

privado
def find_verified_user
if verified_user = env['warden'].user
verified_user
outro
reject_unauthorized_connection
fim
fim
fim
fim

[NOTE] Se você quiser implementar o Action Cable em um aplicativo existente para poder usá-lo, você já implementou outras soluções de autenticação JWT e não deseja atualizar seu código, você sempre pode seguir uma das seguintes correções rápidas .

  1. Se seu token JWT for salvo no localStorage , gere e passe um cookie a partir do frontend logo após o login / login. Você pode usar a seguinte função para pegar o token JWT do localStorage e salvá-lo como um cookie.
 saveTokenAsCookie () { 
 document.cookie = 'X-Authorization =' + this.getToken () + '; caminho = / '; 
 } 

Este cookie estará disponível ao estabelecer a conexão WebSocket no servidor. Para acessar o token JWT a partir dele, o arquivo de conexão deve ficar assim:

 # app / channels / application_cable / connection.rb 
 def find_verified_user 
if current_user = User.find_by (id: JWT.decode (cookies ["X-Autorização"], "", falso) [0] ['sub'])
usuário atual
outro
reject_unauthorized_connection
fim
fim

Essa correção 1) tem problemas de segurança e 2) funciona no local, mas também pode incorrer em problemas de vários domínios na implantação.

2. Passe o userId ao conectar-se ao URL do WebSocket.

 <ActionCableProvider url = {API_WS_ROOT + `? User = $ {this.props.userId} `}> 

No lado do servidor, o arquivo de conexão deve ficar assim:

 # app / channels / application_cable / connection.rb 
 def find_verified_user 
if current_user = User.find_by (id: request.params [: usuário])
usuário atual
outro
reject_unauthorized_connection
fim
fim

Essa correção também tem problemas de segurança.

2. Esquema do Banco de Dados do Rails

Para o exemplo a seguir, o banco de dados Rails será criado seguindo um esquema onde

  • um usuário tem muitas conversas por meio de user_conversations
  • um usuário tem muitas mensagens
  • uma conversa tem muitos (2) usuários através de user_conversations
  • uma conversa tem muitas mensagens
  • uma mensagem pertence a uma conversa e a um usuário
  • uma user_conversation pertence a uma conversa e a um usuário

3. Mensagens Privadas

  • [On Rails] Configure o canal de conversação.

Se você seguiu o Dakota Lillie de artigo para criar o canal de conversas, agora altere-o da seguinte maneira:

 classe ConversationsChannel <ApplicationCable :: Channel 
def subscrito
 #criar um canal genérico onde todos os usuários se conectam 
# stream_from "conversations_channel"

# criar um canal privado para cada usuário
stream_from "current_user _ # {current_user.id}"
fim
 def anulado 
# Qualquer limpeza necessária quando o canal está anulado
fim
fim

Aqui, current_user.id é fornecido por connection.rb na linha identified_by :current_user .

  • [On React] Poste suas novas conversas.

Quando você postar uma nova conversa, inclua o user_id dos usuários remetentes e destinatários. Por exemplo, se você clicar em um apelido ou botão de avatar para iniciar uma conversa com esse usuário específico, uma função handleClick() pode ser algo como isto:

 //StartChatButton.js 
 função fetchToWebsocket (route, bodyData) { 
buscar (`$ {API_ROOT} / $ {route}`, {
método: 'POST',
cabeçalhos: {
"Aceitar": "aplicativo / json",
"Content-Type": "aplicativo / json",
// Apenas, se estivermos salvando o token JWT no localStorage
"Autorização": `Bearer $ {localStorage.getItem (" token ")}`
 //} 
body: JSON.stringify (bodyData)
})
}
 função handleClick () { 
deixe o corpo = {
título: "PRIVATE",
sender_id: props.user_sender_id,
receiver_id: props.user_receiver_id
};

// Se a conversa já existir, execute a função exit ou não faça nada. Caso contrário, busque conversa para WebSockets.
if (conversationExists (props.user_receiver_id)) {
props.exit ();
}
outro {
fetchToWebsocket ("conversações", corpo);
props.exit ();
}
};

A variável body contém os dois usuários que participam da conversa. A variável será usada no servidor como parâmetros para criar uma nova conversa pertencente a ambos os usuários.

Verifique se já existe uma conversa com o mesmo usuário do destinatário para não duplicar conversas. O código de exemplo chama uma função auxiliar de conversationExists(props.user_receiver_id) que verificará se há outra conversa existente com o mesmo usuário na loja.

  • [On Rails] Crie novas conversas e transmita-as.

No arquivo conversations_controller crie uma nova Conversation e duas novas UserConversations , uma para cada usuário na conversa. Serialize a conversa para incluir cada uma das informações dos usuários. Em seguida, transmita a conversa para o canal privado emissor e receptor.

  • [Reagir]. Por fim, ouça o canal de conversa privada com um “componente de cabo”.

react-actioncable-provider fornece um componente ActionCable que atende ao canal de conversação para dados transmitidos. Após a recepção de uma nova conversa, a função handleReceivedConversation verifica se o usuário é um dos usuários na conversa antes de anexar a conversa usando a ação appendNewConversation .

4. Indicadores de presença

  • [On Rails] Configure o canal de presença.

Desta vez, este canal será um canal único e estático. No entanto, após a inscrição, o estado ativo do usuário será alterado para true e após a remoção para false . Essas alterações serão transmitidas para todos os inscritos no presence_chanel . Ao solicitar usuários à API do servidor, os usuários podem conter seus dados de presença (true / false). Agora, com esses valores-chave do usuário, você pode implementar indicadores visuais que mudam quando um usuário (dis) se conecta.

  • [Reagir]. Por fim, ouça o canal de presença com um “componente de cabo”.

react-actioncable-provider fornece um componente ActionCable que atende ao canal de presença para dados transmitidos. Após a recepção de um novo usuário (des) conectado, a função handleReceivedActiveUser verifica o tipo de ação -DO (desconectado) ou CO (conectado), localiza o usuário por id e altera a presença de true para false ou vice-versa.

Onde colocar os componentes do cabo

Finalmente, coloque os componentes do cabo ( presenceCable e ConversationsCable) dentro de um componente que será renderizado apenas uma vez após login / login. No caso de colocar o componente de cabo dentro de um componente que é renderizado mais de uma vez, as funções handleReceived() serão executadas como muitas vezes como o componente é processado.

 //HomeContainer.js 
 const HomeContainer = (props) => { 
 Retorna ( 
<div className = "home-container">
<PresenceCable />
<ConversationsCables />
<Outros componentes />
</ div>
);
};

NOTA sobre a implantação do WebSocket

Para a implantação do Websocket, leia o seguinte artigo Rails em tempo real: Implementando WebSockets no Rails 5 com o Action Cable (por Sophie DeBenedetto).

Texto original em inglês.