Project Templates
Social Network
Chat em Tempo Real para Sua Rede Social
54 min
introdução neste tutorial, você aprenderá como implementar um sistema de mensagens em tempo real para sua aplicação de rede social usando o back4app você construirá uma funcionalidade de chat completa que permite aos usuários enviar e receber mensagens instantaneamente, ver indicadores de digitação e gerenciar conversas recursos essenciais para plataformas sociais envolventes back4app é uma plataforma de backend as a service (baas) construída sobre o parse server que fornece poderosas capacidades em tempo real através de seu recurso live query com a infraestrutura em tempo real do back4app, você pode criar sistemas de mensagens responsivos sem gerenciar servidores websocket complexos ou preocupações com escalabilidade ao final deste tutorial, você terá criado um sistema de mensagens totalmente funcional semelhante ao usado no back4gram, uma aplicação de rede social você implementará a criação de conversas, troca de mensagens em tempo real, indicadores de digitação e busca de usuários, proporcionando aos seus usuários uma experiência de comunicação sem interrupções projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app pré requisitos para completar este tutorial, você precisará de uma conta back4app você pode se inscrever para uma conta gratuita em back4app com https //www back4app com um projeto back4app configurado com o sdk javascript do parse inicializado node js instalado em sua máquina local conhecimento básico de javascript, react js e back4app/parse server um sistema de autenticação já implementado se você ainda não configurou isso, confira nosso tutorial do sistema de autenticação https //www back4app com/docs/react/authentication tutorial familiaridade com hooks do react e ciclo de vida dos componentes passo 1 – entendendo as capacidades em tempo real do back4app antes de começarmos a codificar, vamos entender como o back4app permite a funcionalidade em tempo real através de seu recurso live query live query explicado live query é um recurso do parse server que permite que os clientes se inscrevam em consultas e recebam atualizações quando objetos correspondentes a essas consultas são criados, atualizados ou excluídos isso é perfeito para construir aplicações em tempo real, como sistemas de chat veja como o live query funciona o cliente se inscreve em uma consulta específica (por exemplo, "todas as mensagens na conversa x") quando um objeto correspondente é criado, atualizado ou excluído no servidor o live query notifica automaticamente todos os clientes inscritos os clientes podem então atualizar sua interface de usuário em resposta a esses eventos no contexto de um sistema de mensagens, isso significa quando uma nova mensagem é enviada, todos os usuários daquela conversa a recebem instantaneamente quando um usuário começa a digitar, outros usuários podem ver um indicador de digitação em tempo real quando uma mensagem é lida, os recibos de leitura podem ser atualizados para todos os participantes configurando live query no back4app para usar o live query, você precisa habilitá lo no seu painel do back4app faça login no seu painel do back4app navegue até configurações do servidor > hospedagem web e live query habilite o live query adicione as classes que você deseja usar com o live query (neste caso, "mensagem" e "statusdedigitação") em seguida, no seu código do lado do cliente, você precisa inicializar o cliente live query // initialize parse with your back4app credentials parse initialize("your app id", "your javascript key"); parse serverurl = "https //parseapi back4app com/"; // initialize live query parse livequeryserverurl = "wss\ //your app id back4app io"; agora vamos passar para o design do nosso esquema de banco de dados para nosso sistema de mensagens projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 2 – projetando o esquema do banco de dados para nosso sistema de mensagens, precisaremos de várias classes parse conversa representa um chat entre dois ou mais usuários mensagem representa mensagens individuais dentro de uma conversa statusdedigitação acompanha quando os usuários estão digitando em uma conversa vamos criar essas classes no back4app classe conversa a classe conversation terá os seguintes campos participantes (array de ponteiros para usuário) os usuários envolvidos na conversa últimamensagem (string) o texto da mensagem mais recente atualizadoem (data) atualizado automaticamente pelo parse quando o registro muda classe mensagem a classe mensagem terá conversa (ponteiro para conversa) a conversa à qual esta mensagem pertence remetente (ponteiro para usuário) o usuário que enviou a mensagem texto (string) o conteúdo da mensagem criadoem (data) criado automaticamente pelo parse quando a mensagem é enviada classe typingstatus a classe typingstatus irá rastrear indicadores de digitação usuário (ponteiro para o usuário) o usuário que está digitando conversa (ponteiro para a conversa) a conversa onde a digitação está acontecendo estádigitando (booleano) se o usuário está atualmente digitando agora, vamos configurar a estrutura do nosso projeto para implementar este sistema de mensagens projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 3 – configurando a estrutura do projeto vamos organizar nosso código para o sistema de mensagens vamos nos concentrar nos componentes essenciais src/ ├── components/ │ └── ui/ │ ├── toaster js │ └── avatar js ├── pages/ │ └── messagespage js ├── app js └── parseconfig js vamos criar um simples toaster js para notificações // src/components/ui/toaster js export const toaster = { create ({ title, description, type }) => { // in a real app, you would use a proper toast notification component console log(`\[${type touppercase()}] ${title} ${description}`); alert(`${title} ${description}`); } }; e atualize nosso app js para incluir a rota de mensagens // src/app js import react from 'react'; import { browserrouter as router, routes, route } from 'react router dom'; import messagespage from ' /pages/messagespage'; // import other pages function app() { return ( \<router> \<routes> \<route path="/messages" element={\<messagespage />} /> {/ other routes /} \</routes> \</router> ); } export default app; agora, vamos implementar a funcionalidade principal de mensagens projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 4 – criando o componente da página de mensagens nosso messagespage js componente será o núcleo do nosso sistema de mensagens ele irá exibir uma lista de conversas permitir que os usuários vejam e enviem mensagens mostrar indicadores de digitação habilitar a busca por usuários para iniciar novas conversas vamos construir isso passo a passo // src/pages/messagespage js import react, { usestate, useeffect, useref } from 'react'; import { box, flex, input, button, vstack, text, avatar, heading, spinner, center, hstack, divider, } from '@chakra ui/react'; import { usenavigate } from 'react router dom'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function messagespage() { const \[message, setmessage] = usestate(''); const \[istyping, setistyping] = usestate(false); const \[isloading, setisloading] = usestate(true); const \[issending, setissending] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); const \[conversations, setconversations] = usestate(\[]); const \[activeconversation, setactiveconversation] = usestate(null); const \[messages, setmessages] = usestate(\[]); const \[users, setusers] = usestate(\[]); const \[searchquery, setsearchquery] = usestate(''); const \[showusersearch, setshowusersearch] = usestate(false); const \[processedmessageids, setprocessedmessageids] = usestate(new set()); const \[otherusertyping, setotherusertyping] = usestate(false); const messagesendref = useref(null); const livequerysubscription = useref(null); const typingstatussubscription = useref(null); const typingtimerref = useref(null); const navigate = usenavigate(); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { console log('checking authentication '); const user = await parse user current(); if (!user) { console log('no user found, redirecting to login'); navigate('/login'); return; } console log('user authenticated ', user id, user get('username')); setcurrentuser(user); fetchconversations(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); // clean up subscriptions when component unmounts return () => { if (livequerysubscription current) { livequerysubscription current unsubscribe(); } if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); } if (typingtimerref current) { cleartimeout(typingtimerref current); } }; }, \[navigate]); // we'll implement the following functions next const fetchconversations = async (user) => { // coming up next }; const resetmessagestate = () => { // coming up next }; const fetchmessages = async (conversationid) => { // coming up next }; const setuplivequery = async (conversationid) => { // coming up next }; const setuptypingstatussubscription = async (conversationid) => { // coming up next }; const updatetypingstatus = async (istyping) => { // coming up next }; const sendmessage = async () => { // coming up next }; const searchusers = async (query) => { // coming up next }; const startconversation = async (userid) => { // coming up next }; // format time for messages const formatmessagetime = (date) => { return new date(date) tolocaletimestring(\[], { hour '2 digit', minute '2 digit' }); }; // format date for conversation list const formatconversationdate = (date) => { const now = new date(); const messagedate = new date(date); // if today, show time if (messagedate todatestring() === now\ todatestring()) { return messagedate tolocaletimestring(\[], { hour '2 digit', minute '2 digit' }); } // if this year, show month and day if (messagedate getfullyear() === now\ getfullyear()) { return messagedate tolocaledatestring(\[], { month 'short', day 'numeric' }); } // otherwise show full date return messagedate tolocaledatestring(); }; return ( \<flex h="100vh"> {/ we'll implement the ui next /} \</flex> ); } export default messagespage; agora vamos implementar cada uma das funções necessárias para o nosso sistema de mensagens projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 5 – buscando conversas do usuário primeiro, vamos implementar a fetchconversations função que carrega todas as conversas para o usuário atual const fetchconversations = async (user) => { setisloading(true); try { console log('fetching conversations for user ', user id); // query conversations where the current user is a participant const query = new parse query('conversation'); query equalto('participants', user); query include('participants'); query descending('updatedat'); const results = await query find(); console log('found conversations ', results length); // format conversations const formattedconversations = results map(conv => { const participants = conv get('participants'); // find the other participant (not the current user) const otherparticipant = participants find(p => p id !== user id); if (!otherparticipant) { console warn('could not find other participant in conversation ', conv id); return null; } return { id conv id, user { id otherparticipant id, username otherparticipant get('username'), avatar otherparticipant get('avatar') ? otherparticipant get('avatar') url() null }, lastmessage conv get('lastmessage') || '', updatedat conv get('updatedat') }; }) filter(boolean); // remove any null entries console log('formatted conversations ', formattedconversations); setconversations(formattedconversations); // if there are conversations, set the first one as active if (formattedconversations length > 0) { setactiveconversation(formattedconversations\[0]); fetchmessages(formattedconversations\[0] id); } } catch (error) { console error('error fetching conversations ', error); toaster create({ title 'error', description 'failed to load conversations', type 'error', }); } finally { setisloading(false); } }; esta função consulta a classe conversation para conversas onde o usuário atual é um participante formata os resultados para exibir na interface do usuário define a primeira conversa como ativa se disponível projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 6 – manipulando mensagens e atualizações em tempo real agora vamos implementar as funções para buscar mensagens e configurar atualizações em tempo real usando live query const resetmessagestate = () => { console log('resetting message state'); setmessages(\[]); setprocessedmessageids(new set()); setotherusertyping(false); if (livequerysubscription current) { livequerysubscription current unsubscribe(); livequerysubscription current = null; console log('unsubscribed from live query in resetmessagestate'); } if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); typingstatussubscription current = null; console log('unsubscribed from typing status subscription in resetmessagestate'); } }; const fetchmessages = async (conversationid) => { // reset message state to avoid any lingering messages or subscriptions resetmessagestate(); try { // query messages for this conversation const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); query ascending('createdat'); const results = await query find(); // format messages const formattedmessages = results map(msg => ({ id msg id, text msg get('text'), sender { id msg get('sender') id, username msg get('sender') get('username') }, createdat msg get('createdat') })); // initialize the set of processed message ids const messageids = new set(formattedmessages map(msg => msg id)); // set state after processing all messages setmessages(formattedmessages); setprocessedmessageids(messageids); // set up live query subscription for new messages setuplivequery(conversationid); // set up typing status subscription setuptypingstatussubscription(conversationid); // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); } catch (error) { console error('error fetching messages ', error); toaster create({ title 'error', description 'failed to load messages', type 'error', }); } }; agora, vamos implementar a assinatura do live query para atualizações de mensagens em tempo real const setuplivequery = async (conversationid) => { // capture the current user in a closure to avoid null reference later const captureduser = currentuser; // unsubscribe from previous subscription if exists if (livequerysubscription current) { livequerysubscription current unsubscribe(); console log('unsubscribed from previous live query'); } try { console log('setting up live query for conversation ', conversationid); // create a query that will be used for the subscription const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); console log('created query for message class with conversation id ', conversationid); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened for conversation ', conversationid); }); // handle new messages livequerysubscription current on('create', (message) => { console log('new message received via live query ', message id); // check if we've already processed this message if (processedmessageids has(message id)) { console log('skipping duplicate message ', message id); return; } // format the new message const newmessage = { id message id, text message get('text'), sender { id message get('sender') id, username message get('sender') get('username') }, createdat message get('createdat') }; console log('formatted new message ', newmessage); // add the message id to the set of processed ids setprocessedmessageids(previds => { const newids = new set(previds); newids add(message id); return newids; }); // add the new message to the messages state setmessages(prevmessages => { // check if the message is already in the list (additional duplicate check) if (prevmessages some(msg => msg id === message id)) { console log('message already in list, not adding again ', message id); return prevmessages; } return \[ prevmessages, newmessage]; }); // use the captured user to avoid null reference if (captureduser && message get('sender') id !== captureduser id) { setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === conversationid) { return { conv, lastmessage message get('text'), updatedat message get('createdat') }; } return conv; }); }); } else { // if user check fails, update without the check setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === conversationid) { return { conv, lastmessage message get('text'), updatedat message get('createdat') }; } return conv; }); }); } // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); // handle subscription close livequerysubscription current on('close', () => { console log('live query connection closed for conversation ', conversationid); }); } catch (error) { console error('error setting up live query ', error); toaster create({ title 'error', description 'failed to set up real time messaging', type 'error', }); } }; esta configuração de consulta ao vivo cria uma assinatura para novas mensagens na conversa atual gerencia mensagens recebidas em tempo real atualiza a interface do usuário com novas mensagens à medida que chegam atualiza a lista de conversas com a última mensagem e timestamp gerencia mensagens duplicadas (que podem ocorrer ao enviar e receber via live query) projeto back4gram encontre aqui o código completo para um projeto de amostra de rede social construído com back4app passo 7 – implementando indicadores de digitação para melhorar a experiência do usuário, vamos adicionar indicadores de digitação em tempo real const setuptypingstatussubscription = async (conversationid) => { // cancel previous subscription, if it exists if (typingstatussubscription current) { typingstatussubscription current unsubscribe(); console log('unsubscribed from previous typing status subscription'); } try { console log('setting up typing status subscription for conversation ', conversationid); // create a query for the typingstatus class const query = new parse query('typingstatus'); const conversation = new parse object('conversation'); conversation id = conversationid; // filter by conversation query equalto('conversation', conversation); // don't include the current user's status query notequalto('user', currentuser); // subscribe to the query typingstatussubscription current = await query subscribe(); console log('successfully subscribed to typing status'); // handle create events typingstatussubscription current on('create', (status) => { console log('typing status created ', status get('istyping')); setotherusertyping(status get('istyping')); }); // handle update events typingstatussubscription current on('update', (status) => { console log('typing status updated ', status get('istyping')); setotherusertyping(status get('istyping')); }); // handle errors typingstatussubscription current on('error', (error) => { console error('typing status subscription error ', error); }); } catch (error) { console error('error setting up typing status subscription ', error); } }; const updatetypingstatus = async (istyping) => { if (!activeconversation || !currentuser) return; try { // check if a typing status already exists for this user and conversation const query = new parse query('typingstatus'); const conversation = new parse object('conversation'); conversation id = activeconversation id; query equalto('user', currentuser); query equalto('conversation', conversation); const existingstatus = await query first(); if (existingstatus) { // update existing status existingstatus set('istyping', istyping); await existingstatus save(); } else { // create a new status const typingstatus = parse object extend('typingstatus'); const newstatus = new typingstatus(); newstatus set('user', currentuser); newstatus set('conversation', conversation); newstatus set('istyping', istyping); await newstatus save(); } } catch (error) { console error('error updating typing status ', error); } }; esta implementação assina atualizações de status de digitação de outros usuários na conversa atualiza a interface do usuário quando alguém mais está digitando envia atualizações de status de digitação quando o usuário atual está digitando projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 8 – enviando mensagens agora vamos implementar a funcionalidade de envio de mensagens const sendmessage = async () => { if (!message trim() || !activeconversation) return; const messagetext = message trim(); // store the message text setissending(true); setmessage(''); // clear input immediately to prevent double sending setistyping(false); // clear typing status locally updatetypingstatus(false); // clear typing status on server // clear typing timer if (typingtimerref current) { cleartimeout(typingtimerref current); } try { // create message const message = parse object extend('message'); const newmessage = new message(); // set conversation pointer const conversation = new parse object('conversation'); conversation id = activeconversation id; newmessage set('conversation', conversation); newmessage set('sender', currentuser); newmessage set('text', messagetext); // save the message const savedmessage = await newmessage save(); console log('message saved ', savedmessage id); // add the message id to the set of processed ids to prevent duplication // when it comes back through live query setprocessedmessageids(previds => { const newids = new set(previds); newids add(savedmessage id); return newids; }); // check if this is a new conversation (no messages yet) // if it is, add the message to the ui immediately if (messages length === 0) { console log('first message in conversation, adding to ui immediately'); const formattedmessage = { id savedmessage id, text messagetext, sender { id currentuser id, username currentuser get('username') }, createdat new date() }; setmessages(\[formattedmessage]); // scroll to bottom of messages settimeout(() => { if (messagesendref current) { messagesendref current scrollintoview({ behavior 'smooth' }); } }, 100); } // update conversation's lastmessage const conversationobj = await new parse query('conversation') get(activeconversation id); conversationobj set('lastmessage', messagetext); await conversationobj save(); // update the conversation in the list setconversations(prevconversations => { return prevconversations map(conv => { if (conv id === activeconversation id) { return { conv, lastmessage messagetext, updatedat new date() }; } return conv; }); }); } catch (error) { console error('error sending message ', error); toaster create({ title 'error', description 'failed to send message', type 'error', }); // if there's an error, put the message back in the input setmessage(messagetext); } finally { setissending(false); } }; esta função cria e salva uma nova mensagem no banco de dados back4app atualiza a última mensagem e o timestamp da conversa lida com a prevenção de duplicatas quando a mensagem retorna via live query fornece feedback imediato ao remetente projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 9 – pesquisa de usuário e início de novas conversas vamos implementar a capacidade de buscar usuários e iniciar novas conversas const searchusers = async (query) => { if (!query trim()) { setusers(\[]); return; } try { console log('searching for users with query ', query); // create a query for the user class const userquery = new parse query(parse user); // search for username containing the query string (case insensitive) userquery contains('username', query tolowercase()); // don't include the current user in results userquery notequalto('objectid', currentuser id); // limit to 10 results userquery limit(10); // execute the query const results = await userquery find(); console log('user search results ', results length); // format users const formattedusers = results map(user => ({ id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null })); console log('formatted users ', formattedusers); setusers(formattedusers); } catch (error) { console error('error searching users ', error); toaster create({ title 'error', description 'failed to search users', type 'error', }); } }; const startconversation = async (userid) => { console log('starting conversation with user id ', userid); try { // check if a conversation already exists with this user const query = new parse query('conversation'); // create pointers to both users const currentuserpointer = parse user current(); const otheruserpointer = new parse user(); otheruserpointer id = userid; // find conversations where both users are participants query containsall('participants', \[currentuserpointer, otheruserpointer]); const existingconv = await query first(); if (existingconv) { console log('found existing conversation ', existingconv id); // get the other user object const userquery = new parse query(parse user); const otheruser = await userquery get(userid); // format the conversation const conversation = { id existingconv id, user { id otheruser id, username otheruser get('username'), avatar otheruser get('avatar') ? otheruser get('avatar') url() null }, lastmessage existingconv get('lastmessage') || '', updatedat existingconv get('updatedat') }; // set as active conversation setactiveconversation(conversation); // fetch messages and set up live query await fetchmessages(conversation id); } else { console log('creating new conversation with user ', userid); // get the other user object const userquery = new parse query(parse user); const otheruser = await userquery get(userid); // create a new conversation const conversation = parse object extend('conversation'); const newconversation = new conversation(); // set participants newconversation set('participants', \[currentuserpointer, otheruserpointer]); newconversation set('lastmessage', ''); // save the conversation const savedconv = await newconversation save(); console log('new conversation created ', savedconv id); // format the conversation const conversation = { id savedconv id, user { id otheruser id, username otheruser get('username'), avatar otheruser get('avatar') ? otheruser get('avatar') url() null }, lastmessage '', updatedat savedconv get('updatedat') }; // add to conversations list setconversations(prev => \[conversation, prev]); // set as active conversation and reset message state setactiveconversation(conversation); resetmessagestate(); // set up live query for the new conversation await setuplivequery(savedconv id); // set up typing status subscription await setuptypingstatussubscription(savedconv id); // reset search setshowusersearch(false); setsearchquery(''); setusers(\[]); } } catch (error) { console error('error starting conversation ', error); toaster create({ title 'error', description 'failed to start conversation', type 'error', }); } }; este código fornece duas funções importantes para o nosso sistema de mensagens pesquisa de usuário o searchusers função permite que os usuários encontrem outros usuários pelo nome de usuário ela consulta a classe user no back4app, exclui o usuário atual dos resultados e formata os dados para exibição criação de conversa a startconversation função lida tanto com a busca de conversas existentes quanto com a criação de novas ela verifica se já existe uma conversa entre os usuários se existir, carrega essa conversa e suas mensagens se não existir conversa, cria uma nova configura assinaturas de live query para novas mensagens e status de digitação com essas funções implementadas, os usuários podem facilmente encontrar outros usuários e iniciar conversas com eles, o que é essencial para um sistema de mensagens de rede social projeto back4gram encontre aqui o código completo para um projeto de exemplo de rede social construído com back4app passo 10 – construindo a interface do chat agora que implementamos toda a funcionalidade, vamos criar a interface do usuário para nosso sistema de mensagens a interface consistirá em uma barra lateral listando todas as conversas uma interface de busca para encontrar usuários uma janela de chat mostrando mensagens uma entrada de mensagem com suporte a indicador de digitação aqui está o jsx para nosso componente messagespage return ( \<flex h="100vh"> {/ chat list sidebar /} \<box w="300px" borderrightwidth="1px" bordercolor="gray 600" p={4} bg="gray 800"> \<heading size="md" mb={4}> messages \</heading> \<flex mb={4}> \<input placeholder="search users " value={searchquery} onchange={(e) => { const value = e target value; setsearchquery(value); if (value trim() length > 0) { searchusers(value); } else { setusers(\[]); } }} mr={2} /> \<button onclick={() => { setshowusersearch(!showusersearch); if (!showusersearch) { setsearchquery(''); setusers(\[]); } }} \> {showusersearch ? 'cancel' 'new'} \</button> \</flex> {isloading ? ( \<center py={10}> \<spinner size="lg" /> \</center> ) showusersearch ? ( // user search results \<vstack align="stretch" spacing={2}> {users length > 0 ? ( users map(user => ( \<box key={user id} p={3} borderradius="md" hover={{ bg "gray 700" }} cursor="pointer" onclick={() => startconversation(user id)} \> \<hstack> \<avatar size="sm" name={user username} src={user avatar} /> \<text>{user username}\</text> \</hstack> \</box> )) ) searchquery ? ( \<text color="gray 400" textalign="center">no users found\</text> ) ( \<text color="gray 400" textalign="center">search for users to message\</text> )} \</vstack> ) ( // conversations list \<vstack align="stretch" spacing={0}> {conversations length > 0 ? ( conversations map(conv => ( \<box key={conv id} p={3} borderradius="md" bg={activeconversation? id === conv id ? "gray 700" "transparent"} hover={{ bg "gray 700" }} cursor="pointer" onclick={() => { if (activeconversation? id !== conv id) { setactiveconversation(conv); fetchmessages(conv id); } }} \> \<hstack> \<avatar size="sm" name={conv user username} src={conv user avatar} /> \<box flex="1" overflow="hidden"> \<flex justify="space between" align="center"> \<text fontweight="bold" nooflines={1}>{conv user username}\</text> \<text fontsize="xs" color="gray 400"> {formatconversationdate(conv updatedat)} \</text> \</flex> \<text fontsize="sm" color="gray 400" nooflines={1}> {conv lastmessage || 'no messages yet'} \</text> \</box> \</hstack> \</box> )) ) ( \<text color="gray 400" textalign="center" mt={8}> no conversations yet start a new one! \</text> )} \</vstack> )} \</box> {/ chat window /} \<box flex={1} p={0} display="flex" flexdirection="column" bg="gray 900"> {activeconversation ? ( <> {/ chat header /} \<flex align="center" p={4} borderbottomwidth="1px" bordercolor="gray 700"> \<avatar size="sm" mr={2} name={activeconversation user username} src={activeconversation user avatar} /> \<text fontweight="bold">{activeconversation user username}\</text> {otherusertyping && ( \<text ml={2} color="gray 400" fontsize="sm"> is typing \</text> )} \</flex> {/ messages /} \<box flex={1} p={4} overflowy="auto" display="flex" flexdirection="column" \> {messages length > 0 ? ( messages map((msg) => ( \<box key={msg id} alignself={msg sender id === currentuser id ? "flex end" "flex start"} bg={msg sender id === currentuser id ? "blue 500" "gray 700"} color={msg sender id === currentuser id ? "white" "white"} p={3} borderradius="lg" maxw="70%" mb={2} \> \<text>{msg text}\</text> \<text fontsize="xs" color={msg sender id === currentuser id ? "blue 100" "gray 400"} textalign="right" mt={1}> {formatmessagetime(msg createdat)} \</text> \</box> )) ) ( \<center flex={1}> \<text color="gray 400"> no messages yet say hello! \</text> \</center> )} \<div ref={messagesendref} /> \</box> {/ message input /} \<flex p={4} bordertopwidth="1px" bordercolor="gray 700"> \<input placeholder="type a message " value={message} onchange={(e) => { setmessage(e target value); // set typing status to true locally setistyping(true); // update typing status on server updatetypingstatus(true); // clear any existing timer if (typingtimerref current) { cleartimeout(typingtimerref current); } // set a new timer to turn off typing status after 2 seconds of inactivity typingtimerref current = settimeout(() => { setistyping(false); updatetypingstatus(false); }, 2000); }} mr={2} onkeypress={(e) => { if (e key === 'enter' && !e shiftkey) { e preventdefault(); sendmessage(); // also clear typing status when sending a message setistyping(false); updatetypingstatus(false); if (typingtimerref current) { cleartimeout(typingtimerref current); } } }} /> \<button colorscheme="blue" onclick={sendmessage} isloading={issending} disabled={!message trim() || issending} \> send \</button> \</flex> \</> ) ( \<center flex={1}> \<vstack> \<text fontsize="xl" fontweight="bold">welcome to messages\</text> \<text color="gray 400"> select a conversation or start a new one \</text> \</vstack> \</center> )} \</box> \</flex> ); vamos dividir a interface do usuário em seus principais componentes barra lateral de conversas a barra lateral esquerda mostra uma lista de todas as conversas cada conversa mostra o nome do outro usuário, avatar e a última mensagem os usuários podem clicar em qualquer conversa para visualizar e continuar o chat uma barra de pesquisa na parte superior permite encontrar outros usuários para iniciar novas conversas exibição de mensagens a área principal exibe mensagens para a conversa ativa as mensagens do usuário atual aparecem à direita com um fundo azul as mensagens de outros usuários aparecem à esquerda com um fundo cinza cada mensagem mostra o texto da mensagem e a hora em que foi enviada a lista de mensagens rola automaticamente para baixo quando novas mensagens chegam entrada de mensagem na parte inferior há uma área de entrada para digitar e enviar mensagens os usuários podem pressionar enter para enviar mensagens um botão enviar também permite enviar mensagens digitar na área de entrada aciona automaticamente indicadores de digitação a entrada é desativada até que uma conversa seja selecionada indicadores de digitação quando o outro usuário está digitando, um indicador "está digitando " aparece o indicador é atualizado em tempo real usando a assinatura de consulta ao vivo de status de digitação esta interface fornece uma interface limpa e intuitiva para mensagens, semelhante ao que os usuários esperariam de aplicativos de mensagens populares projeto back4gram encontre aqui o código completo para um projeto de amostra de rede social construído com back4app passo 11 – testando conexões de consulta ao vivo para garantir que nossas conexões de live query estão funcionando corretamente, vamos adicionar uma função de teste que verifica a conexão quando o componente é montado // add this at the beginning of the component to test live query connection useeffect(() => { // test live query connection const testlivequery = async () => { try { console log('testing live query connection '); console log('live query url ', parse livequeryserverurl); const query = new parse query('message'); console log('created test query for message class'); const subscription = await query subscribe(); console log('live query subscription successful!'); subscription on('open', () => { console log('live query connection opened successfully'); }); subscription on('create', (object) => { console log('live query create event received ', object id); }); subscription on('error', (error) => { console error('live query error ', error); }); // unsubscribe after a few seconds to test settimeout(() => { subscription unsubscribe(); console log('unsubscribed from test live query'); }, 10000); } catch (error) { console error('error testing live query ', error); } }; testlivequery(); }, \[]); esta função de teste tenta criar uma inscrição na classe message configura manipuladores de eventos para eventos de conexão registra os resultados de cada etapa cancela automaticamente a inscrição após 10 segundos ao observar os logs do console, você pode verificar que a url da consulta ao vivo está configurada corretamente a conexão pode ser estabelecida os eventos estão sendo recebidos corretamente se você encontrar algum problema com a conexão da consulta ao vivo, verifique o seguinte a consulta ao vivo do back4app está habilitada para seu aplicativo a url da consulta ao vivo está configurada corretamente na sua inicialização do parse a classe mensagem está adicionada à lista de classes da consulta ao vivo no back4app sua rede permite conexões websocket projeto back4gram encontre aqui o código completo para um projeto de amostra de rede social construído com back4app passo 12 – entendendo como o back4app habilita a mensagem em tempo real agora que construímos um sistema de mensagens completo, vamos dar uma olhada mais profunda em como os recursos do back4app tornam a comunicação em tempo real possível infraestrutura de consulta ao vivo o recurso de consulta ao vivo do back4app é construído sobre websockets, que fornecem uma conexão persistente entre o cliente e o servidor isso é fundamentalmente diferente do modelo tradicional de requisição/resposta http api rest tradicional o cliente faz uma requisição → o servidor responde → a conexão é encerrada websockets/consulta ao vivo o cliente estabelece uma conexão persistente → o servidor pode enviar atualizações a qualquer momento essa conexão persistente é o que possibilita as capacidades em tempo real em nosso sistema de mensagens quando uma nova mensagem é criada, o back4app automaticamente envia essa atualização para todos os clientes inscritos sem que eles precisem consultar o servidor modelo de assinatura do parse server o modelo de assinatura no parse server é baseado em consultas isso significa você se inscreve em uma consulta específica (por exemplo, "todas as mensagens na conversa x") você recebe atualizações apenas para objetos que correspondem a essa consulta você pode receber diferentes tipos de eventos ( criar , atualizar , deletar , etc ) essa abordagem baseada em consultas é extremamente poderosa porque permite assinaturas precisas em nosso sistema de mensagens, estamos nos inscrevendo apenas nas mensagens da conversa ativa, o que é muito mais eficiente do que se inscrever em todas as mensagens considerações sobre o banco de dados ao usar live query com back4app, há algumas considerações importantes sobre o banco de dados indexação garanta que os campos usados em consultas de assinatura estejam indexados para melhor desempenho tamanho dos dados mantenha os objetos pequenos para reduzir o tamanho da carga útil e melhorar o desempenho escalonamento conexões de consulta ao vivo usam recursos, então considere isso ao escalar sua aplicação para um desempenho ideal, considere criando índices em campos de conversa na classe mensagem usando ponteiros em vez de embutir grandes objetos gerenciando o ciclo de vida das assinaturas para evitar vazamentos de memória considerações de segurança ao implementar mensagens em tempo real, a segurança é crucial acls e clps use listas de controle de acesso e permissões de nível de classe para proteger seus dados autenticação de conexão apenas usuários autenticados devem ser capazes de estabelecer conexões de live query validação de dados valide o conteúdo da mensagem no lado do servidor usando cloud code limitação de taxa implemente limitação de taxa para prevenir abusos por exemplo, você pode querer usar cloud code para validar e sanitizar mensagens antes que sejam salvas // in cloud code parse cloud beforesave("message", async (request) => { const message = request object; const text = message get("text"); // check if message is empty if (!text || text trim() length === 0) { throw new error("message cannot be empty"); } // sanitize message text message set("text", sanitizehtml(text)); // rate limiting check if user is sending too many messages const sender = message get("sender"); const query = new parse query("message"); query equalto("sender", sender); query greaterthan("createdat", new date(new date() 60000)); // last minute const count = await query count({usemasterkey true}); if (count > 10) { throw new error("you are sending messages too quickly please slow down "); } }); projeto back4gram encontre aqui o código completo para um projeto de amostra de rede social construído com back4app passo 13 – testando e otimizando o sistema de mensagens para garantir que seu sistema de mensagens funcione corretamente, você deve testá lo minuciosamente teste de funcionalidade básica envio de mensagens verifique se as mensagens podem ser enviadas e recebidas criação de conversas teste a criação de novas conversas pesquisa de usuários confirme que os usuários podem ser encontrados e as conversas iniciadas atualizações de ui verifique se os indicadores de digitação, listas de mensagens e listas de conversas são atualizados corretamente teste multi usuário para testar adequadamente um sistema de mensagens, você precisa testar com vários usuários abra duas janelas ou abas do navegador faça login como usuários diferentes em cada um inicie uma conversa entre eles envie mensagens de um lado para o outro teste indicadores de digitação e atualizações em tempo real otimização de desempenho para aplicações maiores, considere estas otimizações paginação carregue mensagens em lotes em vez de todas de uma vez gerenciamento de conexão estabeleça conexões de live query apenas para conversas ativas consultas eficientes use consultas direcionadas e inclua apenas os campos necessários cache implemente cache do lado do cliente para dados acessados com frequência aqui está um exemplo de implementação de paginação para carregamento de mensagens const fetchmessages = async (conversationid, limit = 20, skip = 0) => { try { const query = new parse query('message'); const conversation = new parse object('conversation'); conversation id = conversationid; query equalto('conversation', conversation); query include('sender'); query descending('createdat'); query limit(limit); query skip(skip); const results = await query find(); // process results return { messages formattedmessages, hasmore results length === limit }; } catch (error) { console error('error fetching messages ', error); throw error; } }; tratamento de erros e recuperação um tratamento de erros robusto é crucial para aplicações em tempo real falhas de conexão implemente lógica de reconexão para falhas de websocket falhas de mensagem trate o envio de mensagens falhadas e forneça opções de reenvio recuperação de estado recupere o estado da aplicação após interrupções de conexão por exemplo, você pode adicionar monitoramento de conexão // monitor live query connection status parse livequery on('open', () => { console log('live query connection established'); // re establish subscriptions if needed }); parse livequery on('close', () => { console log('live query connection closed'); // show connection status to user }); parse livequery on('error', (error) => { console error('live query error ', error); // handle error, potentially reconnect }); conclusão neste tutorial, você construiu um sistema abrangente de mensagens em tempo real para sua rede social usando o back4app você implementou mensagens de usuário para usuário criou um sistema onde os usuários podem se encontrar e se enviar mensagens atualizações em tempo real usou o live query do back4app para entregar mensagens instantaneamente indicadores de digitação melhorou a experiência do usuário com notificações de digitação em tempo real gerenciamento de conversas construiu uma interface para visualizar e gerenciar várias conversas o recurso live query do back4app forneceu a infraestrutura em tempo real necessária para tornar isso possível sem ter que gerenciar servidores websocket complexos ou preocupações com escalabilidade ao aproveitar o modelo de assinatura embutido do parse server, você foi capaz de criar um sistema de mensagens responsivo e eficiente o sistema de mensagens que você construiu fornece uma base sólida que você pode aprimorar com recursos adicionais próximos passos confirmações de leitura implementar rastreamento do status de leitura das mensagens conversas em grupo estender o sistema para suportar múltiplos participantes compartilhamento de mídia adicionar suporte para imagens, vídeos e outros anexos reações a mensagens permitir que os usuários reagem às mensagens com emojis exclusão de mensagens implementar a capacidade de excluir ou editar mensagens busca avançada adicionar capacidades de busca dentro das conversas para o código completo da aplicação de rede social back4gram, incluindo seu sistema de mensagens, você pode conferir o repositório do github https //github com/templates back4app/back4gram a combinação de banco de dados, apis, funções em nuvem, armazenamento de arquivos, gerenciamento de usuários e capacidades em tempo real do back4app torna o uma excelente escolha para construir aplicações de rede social ricas em recursos ao usar o back4app, você pode se concentrar em criar uma ótima experiência do usuário em vez de gerenciar uma infraestrutura de backend complexa