Project Templates
Social Network
Chat en temps réel pour votre réseau social
54 min
introduction dans ce tutoriel, vous apprendrez à mettre en œuvre un système de messagerie en temps réel pour votre application de réseau social en utilisant back4app vous construirez une fonctionnalité de chat complète qui permet aux utilisateurs d'envoyer et de recevoir des messages instantanément, de voir des indicateurs de saisie et de gérer des conversations des fonctionnalités essentielles pour des plateformes sociales engageantes back4app est une plateforme backend as a service (baas) construite sur parse server qui fournit de puissantes capacités en temps réel grâce à sa fonctionnalité live query avec l'infrastructure en temps réel de back4app, vous pouvez créer des systèmes de messagerie réactifs sans gérer des serveurs websocket complexes ou des préoccupations de mise à l'échelle à la fin de ce tutoriel, vous aurez créé un système de messagerie entièrement fonctionnel similaire à celui utilisé dans back4gram, une application de réseau social vous mettrez en œuvre la création de conversations, l'échange de messages en temps réel, les indicateurs de saisie et la recherche d'utilisateurs, offrant à vos utilisateurs une expérience de communication fluide projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app prérequis pour compléter ce tutoriel, vous aurez besoin de un compte back4app vous pouvez vous inscrire pour un compte gratuit sur back4app com https //www back4app com un projet back4app configuré avec le sdk javascript parse initialisé node js installé sur votre machine locale connaissances de base en javascript, react js et back4app/parse server un système d'authentification déjà mis en place si vous ne l'avez pas encore configuré, consultez notre tutoriel sur le système d'authentification https //www back4app com/docs/react/authentication tutorial familiarité avec les hooks react et le cycle de vie des composants étape 1 – comprendre les capacités en temps réel de back4app avant de commencer à coder, comprenons comment back4app permet une fonctionnalité en temps réel grâce à sa fonctionnalité live query explication de live query live query est une fonctionnalité de parse server qui permet aux clients de s'abonner à des requêtes et de recevoir des mises à jour lorsque des objets correspondant à ces requêtes sont créés, mis à jour ou supprimés c'est parfait pour construire des applications en temps réel comme des systèmes de chat voici comment fonctionne live query le client s'abonne à une requête spécifique (par exemple, "tous les messages dans la conversation x") lorsqu'un objet correspondant est créé, mis à jour ou supprimé sur le serveur live query notifie automatiquement tous les clients abonnés les clients peuvent alors mettre à jour leur interface utilisateur en réponse à ces événements dans le contexte d'un système de messagerie, cela signifie lorsqu'un nouveau message est envoyé, tous les utilisateurs de cette conversation le reçoivent instantanément lorsqu'un utilisateur commence à taper, les autres utilisateurs peuvent voir un indicateur de saisie en temps réel lorsqu'un message est lu, les accusés de lecture peuvent être mis à jour pour tous les participants configuration de live query sur back4app pour utiliser live query, vous devez l'activer dans votre tableau de bord back4app connectez vous à votre tableau de bord back4app accédez à paramètres du serveur > hébergement web et live query activer live query ajoutez les classes que vous souhaitez utiliser avec live query (dans notre cas, "message" et "typingstatus") ensuite, dans votre code côté client, vous devez initialiser le client 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"; passons maintenant à la conception de notre schéma de base de données pour notre système de messagerie projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 2 – conception du schéma de base de données pour notre système de messagerie, nous aurons besoin de plusieurs classes parse conversation représente une discussion entre deux ou plusieurs utilisateurs message représente des messages individuels au sein d'une conversation typingstatus suit quand les utilisateurs sont en train de taper dans une conversation créons ces classes dans back4app classe conversation la classe conversation aura les champs suivants participants (tableau de pointeurs vers l'utilisateur) les utilisateurs impliqués dans la conversation lastmessage (chaîne) le texte du message le plus récent updatedat (date) mis à jour automatiquement par parse lorsque l'enregistrement change classe message la classe message aura conversation (pointeur vers la conversation) la conversation à laquelle ce message appartient sender (pointeur vers l'utilisateur) l'utilisateur qui a envoyé le message text (chaîne) le contenu du message createdat (date) créé automatiquement par parse lorsque le message est envoyé classe typingstatus la classe typingstatus suivra les indicateurs de saisie utilisateur (pointeur vers l'utilisateur) l'utilisateur qui est en train de taper conversation (pointeur vers la conversation) la conversation où la saisie a lieu estentraindetaper (booléen) indique si l'utilisateur est actuellement en train de taper maintenant, configurons notre structure de projet pour mettre en œuvre ce système de messagerie projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 3 – configuration de la structure du projet organisons notre code pour le système de messagerie nous nous concentrerons sur les composants essentiels src/ ├── components/ │ └── ui/ │ ├── toaster js │ └── avatar js ├── pages/ │ └── messagespage js ├── app js └── parseconfig js créons un simple toaster js pour les notifications // 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}`); } }; et mettons à jour notre app js pour inclure la route des messages // 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; maintenant, implémentons la fonctionnalité principale de messagerie projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 4 – création du composant de la page des messages notre messagespage js composant sera le cœur de notre système de messagerie il va afficher une liste de conversations permettre aux utilisateurs de voir et d'envoyer des messages afficher les indicateurs de saisie activer la recherche d'utilisateurs pour commencer de nouvelles conversations construisons cela étape par étape // 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; implémentons maintenant chacune des fonctions nécessaires à notre système de messagerie projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 5 – récupération des conversations utilisateur tout d'abord, implémentons la fetchconversations fonction qui charge toutes les conversations pour l'utilisateur actuel 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); } }; cette fonction interroge la classe conversation pour les conversations où l'utilisateur actuel est un participant formate les résultats pour les afficher dans l'interface utilisateur définit la première conversation comme active si disponible projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 6 – gestion des messages et mises à jour en temps réel maintenant, implémentons les fonctions pour récupérer les messages et configurer les mises à jour en temps réel en utilisant 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', }); } }; maintenant, implémentons l'abonnement live query pour les mises à jour de messages en temps réel 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', }); } }; cette configuration de requête en direct crée un abonnement aux nouveaux messages dans la conversation actuelle gère les messages entrants en temps réel met à jour l'interface utilisateur avec les nouveaux messages à leur arrivée met à jour la liste de conversation avec le dernier message et l'horodatage gère les messages en double (qui peuvent se produire lors de l'envoi et de la réception via live query) projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 7 – mise en œuvre des indicateurs de saisie pour améliorer l'expérience utilisateur, ajoutons des indicateurs de saisie en temps réel 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); } }; cette mise en œuvre s'abonne aux mises à jour de statut de saisie des autres utilisateurs dans la conversation met à jour l'interface utilisateur lorsque quelqu'un d'autre est en train de taper envoie des mises à jour de statut de saisie lorsque l'utilisateur actuel est en train de taper projet back4gram trouver ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 8 – envoi de messages maintenant, implémentons la fonctionnalité d'envoi de messages 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); } }; cette fonction crée et enregistre un nouveau message dans la base de données back4app met à jour le dernier message et l'horodatage de la conversation gère la prévention des doublons lorsque le message revient via live query fournit un retour immédiat à l'expéditeur projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 9 – recherche d'utilisateur et démarrage de nouvelles conversations implémentons la possibilité de rechercher des utilisateurs et de commencer de nouvelles conversations 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', }); } }; ce code fournit deux fonctions importantes pour notre système de messagerie recherche d'utilisateur le searchusers fonction permet aux utilisateurs de trouver d'autres utilisateurs par nom d'utilisateur elle interroge la classe user dans back4app, exclut l'utilisateur actuel des résultats et formate les données pour l'affichage création de conversation la startconversation fonction gère à la fois la recherche de conversations existantes et la création de nouvelles elle vérifie si une conversation existe déjà entre les utilisateurs si une conversation existe, elle charge cette conversation et ses messages si aucune conversation n'existe, elle en crée une nouvelle met en place des abonnements live query pour les nouveaux messages et le statut de saisie avec ces fonctions mises en œuvre, les utilisateurs peuvent facilement trouver d'autres utilisateurs et commencer des conversations avec eux, ce qui est essentiel pour un système de messagerie de réseau social projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 10 – construction de l'interface utilisateur de chat maintenant que nous avons mis en œuvre toutes les fonctionnalités, créons l'interface utilisateur pour notre système de messagerie l'interface consistera en une barre latérale listant toutes les conversations une interface de recherche pour trouver des utilisateurs une fenêtre de chat affichant les messages un champ de message avec support d'indicateur de saisie voici le jsx pour notre composant 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> ); décomposons l'interface utilisateur en ses principaux composants barre latérale des conversations la barre latérale gauche montre une liste de toutes les conversations chaque conversation montre le nom de l'autre utilisateur, son avatar et le dernier message les utilisateurs peuvent cliquer sur n'importe quelle conversation pour voir et continuer le chat une barre de recherche en haut permet de trouver d'autres utilisateurs pour commencer de nouvelles conversations affichage des messages la zone principale affiche les messages de la conversation active les messages de l'utilisateur actuel apparaissent à droite avec un fond bleu les messages des autres utilisateurs apparaissent à gauche avec un fond gris chaque message montre le texte du message et l'heure à laquelle il a été envoyé la liste des messages défile automatiquement vers le bas lorsque de nouveaux messages arrivent saisie de message en bas se trouve une zone de saisie pour taper et envoyer des messages les utilisateurs peuvent appuyer sur entrée pour envoyer des messages un bouton envoyer permet également d'envoyer des messages taper dans la zone de saisie déclenche automatiquement des indicateurs de saisie la saisie est désactivée jusqu'à ce qu'une conversation soit sélectionnée indicateurs de saisie lorsque l'autre utilisateur tape, un indicateur "est en train de taper " apparaît l'indicateur est mis à jour en temps réel grâce à l'abonnement typingstatus live query cette interface utilisateur fournit une interface propre et intuitive pour la messagerie, similaire à ce que les utilisateurs attendraient des applications de messagerie populaires projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 11 – tester les connexions live query pour s'assurer que nos connexions live query fonctionnent correctement, ajoutons une fonction de test qui vérifie la connexion lorsque le composant se monte // 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(); }, \[]); cette fonction de test tente de créer un abonnement à la classe message configure des gestionnaires d'événements pour les événements de connexion enregistre les résultats de chaque étape se désabonne automatiquement après 10 secondes en regardant les journaux de la console, vous pouvez vérifier que l'url de la requête en direct est correctement configuré la connexion peut être établie les événements sont correctement reçus si vous rencontrez des problèmes avec la connexion de la requête en direct, vérifiez les éléments suivants back4app live query est activé pour votre application l'url de la requête en direct est correctement définie dans votre initialisation parse la classe message est ajoutée à la liste des classes de requête en direct dans back4app votre réseau permet les connexions websocket projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 12 – comprendre comment back4app permet la messagerie en temps réel maintenant que nous avons construit un système de messagerie complet, examinons de plus près comment les fonctionnalités de back4app rendent la messagerie en temps réel possible infrastructure de requête en direct la fonctionnalité de requête en direct de back4app est construite sur des websockets, qui fournissent une connexion persistante entre le client et le serveur cela est fondamentalement différent du modèle traditionnel de requête/réponse http api rest traditionnelle le client fait une demande → le serveur répond → la connexion se ferme websockets/requête en direct le client établit une connexion persistante → le serveur peut envoyer des mises à jour à tout moment cette connexion persistante est ce qui permet les capacités en temps réel dans notre système de messagerie lorsqu'un nouveau message est créé, back4app pousse automatiquement cette mise à jour à tous les clients abonnés sans qu'ils aient besoin d'interroger le serveur modèle d'abonnement de parse server le modèle d'abonnement dans parse server est basé sur des requêtes cela signifie vous vous abonnez à une requête spécifique (par exemple, "tous les messages dans la conversation x") vous recevez des mises à jour uniquement pour les objets qui correspondent à cette requête vous pouvez recevoir différents types d'événements ( création , mise à jour , suppression , etc ) cette approche basée sur les requêtes est extrêmement puissante car elle permet des abonnements précis dans notre système de messagerie, nous ne nous abonnons qu'aux messages de la conversation active, ce qui est beaucoup plus efficace que de s'abonner à tous les messages considérations sur la base de données lors de l'utilisation de live query avec back4app, il y a quelques considérations importantes concernant la base de données indexation assurez vous que les champs utilisés dans les requêtes d'abonnement sont indexés pour de meilleures performances taille des données gardez les objets petits pour réduire la taille de la charge utile et améliorer les performances mise à l'échelle les connexions de requête en direct utilisent des ressources, donc tenez en compte lors de la mise à l'échelle de votre application pour des performances optimales, considérez créer des index sur conversation champs dans la classe message utiliser des pointeurs plutôt que d'incorporer de grands objets gérer le cycle de vie des abonnements pour éviter les fuites de mémoire considérations de sécurité lors de la mise en œuvre de la messagerie en temps réel, la sécurité est cruciale acls et clps utilisez des listes de contrôle d'accès et des autorisations au niveau de la classe pour protéger vos données authentification de connexion seules les utilisateurs authentifiés devraient pouvoir établir des connexions de requête en direct validation des données validez le contenu des messages côté serveur en utilisant le cloud code limitation de débit implémentez une limitation de débit pour prévenir les abus par exemple, vous pourriez vouloir utiliser le cloud code pour valider et assainir les messages avant qu'ils ne soient enregistrés // 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 "); } }); projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 13 – tester et optimiser le système de messagerie pour garantir le bon fonctionnement de votre système de messagerie, vous devez le tester en profondeur tests de fonctionnalité de base envoi de messages vérifiez que les messages peuvent être envoyés et reçus création de conversations testez la création de nouvelles conversations recherche d'utilisateurs confirmez que les utilisateurs peuvent être trouvés et que des conversations peuvent être commencées mises à jour de l'interface utilisateur vérifiez que les indicateurs de saisie, les listes de messages et les listes de conversations se mettent à jour correctement tests multi utilisateurs pour tester correctement un système de messagerie, vous devez tester avec plusieurs utilisateurs ouvrez deux fenêtres ou onglets de navigateur connectez vous en tant qu'utilisateurs différents dans chaque démarrez une conversation entre eux envoyez des messages en aller retour testez les indicateurs de saisie et les mises à jour en temps réel optimisation des performances pour les applications plus grandes, envisagez ces optimisations pagination chargez les messages par lots plutôt que tous en même temps gestion des connexions établissez uniquement des connexions live query pour les conversations actives requêtes efficaces utilisez des requêtes ciblées et incluez uniquement les champs nécessaires mise en cache implémentez la mise en cache côté client pour les données fréquemment consultées voici un exemple d'implémentation de la pagination pour le chargement des messages 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; } }; gestion des erreurs et récupération une gestion robuste des erreurs est cruciale pour les applications en temps réel pannes de connexion implémentez une logique de reconnexion pour les pannes de websocket pannes de message gérez les envois de messages échoués et proposez des options de réessai récupération d'état récupérez l'état de l'application après des interruptions de connexion par exemple, vous pourriez ajouter une surveillance de connexion // 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 }); conclusion dans ce tutoriel, vous avez construit un système de messagerie en temps réel complet pour votre réseau social en utilisant back4app vous avez mis en œuvre messagerie utilisateur à utilisateur créé un système où les utilisateurs peuvent se trouver et s'envoyer des messages mises à jour en temps réel utilisé live query de back4app pour livrer des messages instantanément indicateurs de saisie amélioré l'expérience utilisateur avec des notifications de saisie en temps réel gestion des conversations construit une interface utilisateur pour visualiser et gérer plusieurs conversations la fonctionnalité live query de back4app a fourni l'infrastructure en temps réel nécessaire pour rendre cela possible sans avoir à gérer des serveurs websocket complexes ou des préoccupations de mise à l'échelle en tirant parti du modèle d'abonnement intégré de parse server, vous avez pu créer un système de messagerie réactif et efficace le système de messagerie que vous avez construit fournit une base solide que vous pouvez améliorer avec des fonctionnalités supplémentaires prochaines étapes accusés de réception implémenter le suivi de l'état de lecture des messages conversations de groupe étendre le système pour prendre en charge plusieurs participants partage de médias ajouter le support pour les images, vidéos et autres pièces jointes réactions aux messages permettre aux utilisateurs de réagir aux messages avec des emojis suppression de messages implémenter la possibilité de supprimer ou d'éditer des messages recherche avancée ajouter des capacités de recherche au sein des conversations pour le code complet de l'application de réseau social back4gram, y compris son système de messagerie, vous pouvez consulter le dépôt github https //github com/templates back4app/back4gram la combinaison de base de données, d'apis, de fonctions cloud, de stockage de fichiers, de gestion des utilisateurs et de capacités en temps réel de back4app en fait un excellent choix pour construire des applications de réseau social riches en fonctionnalités en utilisant back4app, vous pouvez vous concentrer sur la création d'une excellente expérience utilisateur plutôt que de gérer une infrastructure backend complexe