Project Templates
Social Network
Chat en Tiempo Real para Tu Red Social
54 min
introducción en este tutorial, aprenderás cómo implementar un sistema de mensajería en tiempo real para tu aplicación de red social utilizando back4app construirás una funcionalidad de chat completa que permite a los usuarios enviar y recibir mensajes al instante, ver indicadores de escritura y gestionar conversaciones, características esenciales para plataformas sociales atractivas back4app es una plataforma de backend as a service (baas) construida sobre parse server que proporciona potentes capacidades en tiempo real a través de su función de live query con la infraestructura en tiempo real de back4app, puedes crear sistemas de mensajería receptivos sin gestionar servidores websocket complejos o preocupaciones de escalado al final de este tutorial, habrás creado un sistema de mensajería completamente funcional similar al que se utiliza en back4gram, una aplicación de red social implementarás la creación de conversaciones, el intercambio de mensajes en tiempo real, indicadores de escritura y búsqueda de usuarios, brindando a tus usuarios una experiencia de comunicación fluida proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app requisitos previos para completar este tutorial, necesitarás una cuenta de back4app puedes registrarte para obtener una cuenta gratuita en back4app com https //www back4app com un proyecto de back4app configurado con el sdk de javascript de parse inicializado node js instalado en tu máquina local conocimientos básicos de javascript, react js y back4app/parse server un sistema de autenticación ya implementado si aún no lo has configurado, consulta nuestro tutorial del sistema de autenticación https //www back4app com/docs/react/authentication tutorial familiaridad con los hooks de react y el ciclo de vida de los componentes paso 1 – entendiendo las capacidades en tiempo real de back4app antes de comenzar a codificar, entendamos cómo back4app habilita la funcionalidad en tiempo real a través de su característica live query explicación de live query live query es una característica de parse server que permite a los clientes suscribirse a consultas y recibir actualizaciones cuando se crean, actualizan o eliminan objetos que coinciden con esas consultas esto es perfecto para construir aplicaciones en tiempo real como sistemas de chat así es como funciona live query el cliente se suscribe a una consulta específica (por ejemplo, "todos los mensajes en la conversación x") cuando se crea, actualiza o elimina un objeto coincidente en el servidor live query notifica automáticamente a todos los clientes suscritos los clientes pueden entonces actualizar su interfaz de usuario en respuesta a estos eventos en el contexto de un sistema de mensajería, esto significa cuando se envía un nuevo mensaje, todos los usuarios en esa conversación lo reciben instantáneamente cuando un usuario comienza a escribir, otros usuarios pueden ver un indicador de escritura en tiempo real cuando un mensaje es leído, se pueden actualizar los recibos de lectura para todos los participantes configurando live query en back4app para usar live query, necesitas habilitarlo en tu panel de back4app inicia sesión en tu panel de back4app navega a configuración del servidor > alojamiento web y live query habilitar live query agrega las clases que deseas usar con live query (en nuestro caso, "mensaje" y "estadodeescritura") a continuación, en tu código del lado del cliente, necesitas inicializar el cliente de 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"; ahora pasemos a diseñar nuestro esquema de base de datos para nuestro sistema de mensajería proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 2 – diseñando el esquema de la base de datos para nuestro sistema de mensajería, necesitaremos varias clases de parse conversación representa un chat entre dos o más usuarios mensaje representa mensajes individuales dentro de una conversación estadodeescritura rastrea cuándo los usuarios están escribiendo en una conversación creamos estas clases en back4app clase de conversación la clase conversation tendrá los siguientes campos participantes (array de punteros a usuario) los usuarios involucrados en la conversación últimomensaje (cadena) el texto del mensaje más reciente actualizadoen (fecha) actualizado automáticamente por parse cuando el registro cambia clase mensaje la clase mensaje tendrá conversación (puntero a conversación) la conversación a la que pertenece este mensaje remitente (puntero a usuario) el usuario que envió el mensaje texto (cadena) el contenido del mensaje creadoen (fecha) creado automáticamente por parse cuando se envía el mensaje clase typingstatus la clase typingstatus rastreará los indicadores de escritura usuario (puntero a usuario) el usuario que está escribiendo conversación (puntero a conversación) la conversación donde se está escribiendo estáescribiendo (booleano) si el usuario está escribiendo actualmente ahora, configuremos la estructura de nuestro proyecto para implementar este sistema de mensajería proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 3 – configuración de la estructura del proyecto organizaremos nuestro código para el sistema de mensajería nos centraremos en los componentes esenciales src/ ├── components/ │ └── ui/ │ ├── toaster js │ └── avatar js ├── pages/ │ └── messagespage js ├── app js └── parseconfig js creamos un simple toaster js para notificaciones // 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}`); } }; y actualizamos nuestro app js para incluir la ruta de mensajes // 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; ahora, implementemos la funcionalidad principal de mensajería proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 4 – creando el componente de la página de mensajes nuestro messagespage js componente será el núcleo de nuestro sistema de mensajería este mostrar una lista de conversaciones permitir a los usuarios ver y enviar mensajes mostrar indicadores de escritura habilitar la búsqueda de usuarios para iniciar nuevas conversaciones construyamos esto paso a paso // 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; ahora implementemos cada una de las funciones necesarias para nuestro sistema de mensajería proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 5 – recuperando conversaciones de usuario primero, implementemos la fetchconversations función que carga todas las conversaciones para el usuario actual 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 función consulta la clase conversation para conversaciones donde el usuario actual es un participante formatea los resultados para mostrar en la interfaz de usuario establece la primera conversación como activa si está disponible proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 6 – manejo de mensajes y actualizaciones en tiempo real ahora implementemos las funciones para obtener mensajes y configurar actualizaciones en tiempo real utilizando 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', }); } }; ahora, implementemos la suscripción de live query para actualizaciones de mensajes en tiempo 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 configuración de consulta en vivo crea una suscripción a nuevos mensajes en la conversación actual maneja mensajes entrantes en tiempo real actualiza la interfaz de usuario con nuevos mensajes a medida que llegan actualiza la lista de conversaciones con el último mensaje y la marca de tiempo gestiona mensajes duplicados (que pueden ocurrir al enviar y recibir a través de live query) proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 7 – implementando indicadores de escritura para mejorar la experiencia del usuario, agreguemos indicadores de escritura en tiempo 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 implementación se suscribe a las actualizaciones del estado de escritura de otros usuarios en la conversación actualiza la interfaz de usuario cuando alguien más está escribiendo envía actualizaciones del estado de escritura cuando el usuario actual está escribiendo proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 8 – enviando mensajes ahora implementemos la funcionalidad de envío de mensajes 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 función crea y guarda un nuevo mensaje en la base de datos de back4app actualiza el último mensaje y la marca de tiempo de la conversación maneja la prevención de duplicados cuando el mensaje regresa a través de live query proporciona retroalimentación inmediata al remitente proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 9 – búsqueda de usuarios y comenzando nuevas conversaciones implementemos la capacidad de buscar usuarios y comenzar nuevas conversaciones 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 proporciona dos funciones importantes para nuestro sistema de mensajería búsqueda de usuarios el searchusers función permite a los usuarios encontrar otros usuarios por nombre de usuario consulta la clase user en back4app, excluye al usuario actual de los resultados y formatea los datos para su visualización creación de conversaciones la startconversation función maneja tanto la búsqueda de conversaciones existentes como la creación de nuevas esta verifica si ya existe una conversación entre los usuarios si existe, carga esa conversación y sus mensajes si no existe una conversación, crea una nueva configura suscripciones de live query para nuevos mensajes y estado de escritura con estas funciones implementadas, los usuarios pueden encontrar fácilmente a otros usuarios y comenzar conversaciones con ellos, lo cual es esencial para un sistema de mensajería en redes sociales proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 10 – construyendo la interfaz de chat ahora que hemos implementado toda la funcionalidad, vamos a crear la interfaz de usuario para nuestro sistema de mensajería la interfaz consistirá en una barra lateral que enumera todas las conversaciones una interfaz de búsqueda para encontrar usuarios una ventana de chat que muestra mensajes una entrada de mensaje con soporte para indicador de escritura aquí está el jsx para nuestro 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> ); desglosemos la interfaz de usuario en sus componentes principales barra lateral de conversaciones la barra lateral izquierda muestra una lista de todas las conversaciones cada conversación muestra el nombre del otro usuario, su avatar y el último mensaje los usuarios pueden hacer clic en cualquier conversación para ver y continuar el chat una barra de búsqueda en la parte superior permite encontrar otros usuarios para iniciar nuevas conversaciones visualización de mensajes el área principal muestra los mensajes de la conversación activa los mensajes del usuario actual aparecen a la derecha con un fondo azul los mensajes de otros usuarios aparecen a la izquierda con un fondo gris cada mensaje muestra el texto del mensaje y la hora en que fue enviado la lista de mensajes se desplaza automáticamente hacia abajo cuando llegan nuevos mensajes entrada de mensajes en la parte inferior hay un área de entrada para escribir y enviar mensajes los usuarios pueden presionar enter para enviar mensajes un botón de enviar también permite enviar mensajes escribir en el área de entrada activa automáticamente los indicadores de escritura la entrada está deshabilitada hasta que se seleccione una conversación indicadores de escritura cuando el otro usuario está escribiendo, aparece un indicador de "está escribiendo " el indicador se actualiza en tiempo real utilizando la suscripción de live query de typingstatus esta interfaz de usuario proporciona una interfaz limpia e intuitiva para mensajería, similar a lo que los usuarios esperarían de aplicaciones de mensajería populares proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 11 – probando conexiones de live query para asegurar que nuestras conexiones de live query están funcionando correctamente, agreguemos una función de prueba que verifique la conexión cuando se monte el componente // 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 función de prueba intenta crear una suscripción a la clase message configura controladores de eventos para eventos de conexión registra los resultados de cada paso se cancela automáticamente después de 10 segundos al observar los registros de la consola, puedes verificar que la url de live query está correctamente configurada se puede establecer la conexión los eventos se están recibiendo correctamente si encuentras algún problema con la conexión de live query, verifica lo siguiente back4app live query está habilitado para tu aplicación la url de live query está correctamente configurada en tu inicialización de parse la clase message está añadida a la lista de clases de live query en back4app tu red permite conexiones websocket proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 12 – entendiendo cómo back4app habilita la mensajería en tiempo real ahora que hemos construido un sistema de mensajería completo, echemos un vistazo más profundo a cómo las características de back4app hacen posible la mensajería en tiempo real infraestructura de consulta en vivo la función de consulta en vivo de back4app se basa en websockets, que proporcionan una conexión persistente entre el cliente y el servidor esto es fundamentalmente diferente del modelo tradicional de solicitud/respuesta http api rest tradicional el cliente hace una solicitud → el servidor responde → la conexión se cierra websockets/consulta en vivo el cliente establece una conexión persistente → el servidor puede enviar actualizaciones en cualquier momento esta conexión persistente es lo que permite las capacidades en tiempo real en nuestro sistema de mensajería cuando se crea un nuevo mensaje, back4app envía automáticamente esa actualización a todos los clientes suscritos sin que tengan que consultar al servidor modelo de suscripción de parse server el modelo de suscripción en parse server se basa en consultas esto significa te suscribes a una consulta específica (por ejemplo, "todos los mensajes en la conversación x") recibes actualizaciones solo para objetos que coinciden con esa consulta puedes recibir diferentes tipos de eventos ( crear , actualizar , eliminar , etc ) este enfoque basado en consultas es extremadamente poderoso porque permite suscripciones precisas en nuestro sistema de mensajería, solo nos estamos suscribiendo a los mensajes de la conversación activa, lo que es mucho más eficiente que suscribirse a todos los mensajes consideraciones de la base de datos al usar live query con back4app, hay algunas consideraciones importantes de la base de datos indexación asegúrese de que los campos utilizados en las consultas de suscripción estén indexados para un mejor rendimiento tamaño de datos mantenga los objetos pequeños para reducir el tamaño de la carga útil y mejorar el rendimiento escalado las conexiones de consulta en vivo utilizan recursos, así que considere esto al escalar su aplicación para un rendimiento óptimo, considere crear índices en campos de conversación en la clase mensaje usar punteros en lugar de incrustar objetos grandes gestionar el ciclo de vida de las suscripciones para evitar fugas de memoria consideraciones de seguridad al implementar mensajería en tiempo real, la seguridad es crucial acls y clps utiliza listas de control de acceso y permisos a nivel de clase para proteger tus datos autenticación de conexión solo los usuarios autenticados deberían poder establecer conexiones de consulta en vivo validación de datos valida el contenido del mensaje en el lado del servidor utilizando cloud code limitación de tasa implementa limitación de tasa para prevenir abusos por ejemplo, podrías querer usar cloud code para validar y sanitizar mensajes antes de que se guarden // 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 "); } }); proyecto back4gram encuentra aquí el código completo para un proyecto de muestra de red social construido con back4app paso 13 – pruebas y optimización del sistema de mensajería para asegurar que su sistema de mensajería funcione correctamente, debe probarlo a fondo pruebas de funcionalidad básica envío de mensajes verifique que los mensajes se puedan enviar y recibir creación de conversaciones pruebe crear nuevas conversaciones búsqueda de usuarios confirme que se pueden encontrar usuarios y comenzar conversaciones actualizaciones de ui verifique que los indicadores de escritura, listas de mensajes y listas de conversaciones se actualicen correctamente pruebas multi usuario para probar adecuadamente un sistema de mensajería, necesita probar con múltiples usuarios abre dos ventanas o pestañas del navegador inicia sesión como diferentes usuarios en cada uno inicia una conversación entre ellos envía mensajes de ida y vuelta prueba los indicadores de escritura y las actualizaciones en tiempo real optimización del rendimiento para aplicaciones más grandes, considera estas optimizaciones paginación carga mensajes en lotes en lugar de todos a la vez gestión de conexiones solo establece conexiones de live query para conversaciones activas consultas eficientes utiliza consultas específicas e incluye solo los campos necesarios caché implementa caché del lado del cliente para datos de acceso frecuente aquí hay un ejemplo de implementación de paginación para la carga de mensajes 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; } }; manejo de errores y recuperación el manejo robusto de errores es crucial para aplicaciones en tiempo real fallos de conexión implementar lógica de reconexión para fallos de websocket fallos de mensajes manejar envíos de mensajes fallidos y proporcionar opciones de reintento recuperación de estado recuperar el estado de la aplicación después de interrupciones de conexión por ejemplo, podrías agregar monitoreo de conexión // 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 }); conclusión en este tutorial, has construido un sistema de mensajería en tiempo real completo para tu red social utilizando back4app has implementado mensajería de usuario a usuario creado un sistema donde los usuarios pueden encontrarse y enviarse mensajes actualizaciones en tiempo real usado live query de back4app para entregar mensajes instantáneamente indicadores de escritura mejorada la experiencia del usuario con notificaciones de escritura en tiempo real gestión de conversaciones construido una interfaz para ver y gestionar múltiples conversaciones la función live query de back4app proporcionó la infraestructura en tiempo real necesaria para hacer esto posible sin tener que gestionar servidores websocket complejos o preocupaciones de escalado al aprovechar el modelo de suscripción integrado de parse server, pudiste crear un sistema de mensajería receptivo y eficiente el sistema de mensajería que has construido proporciona una base sólida que puedes mejorar con características adicionales próximos pasos confirmaciones de lectura implementar el seguimiento del estado de lectura de los mensajes conversaciones en grupo ampliar el sistema para soportar múltiples participantes compartición de medios agregar soporte para imágenes, videos y otros archivos adjuntos reacciones a mensajes permitir a los usuarios reaccionar a los mensajes con emojis eliminación de mensajes implementar la capacidad de eliminar o editar mensajes búsqueda avanzada agregar capacidades de búsqueda dentro de las conversaciones para el código completo de la aplicación de red social back4gram, incluyendo su sistema de mensajería, puedes consultar el repositorio de github https //github com/templates back4app/back4gram la combinación de base de datos, apis, funciones en la nube, almacenamiento de archivos, gestión de usuarios y capacidades en tiempo real de back4app lo convierte en una excelente opción para construir aplicaciones de redes sociales ricas en características al usar back4app, puedes concentrarte en crear una gran experiencia de usuario en lugar de gestionar una infraestructura de backend compleja