Comment créer des consultations vidéo privées dans Next.js

Une consultation vidéo ressemble à un seul appel, et dans une application Next.js, ce sont plusieurs fonctionnalités. Une personne réserve une demi-heure avec une médecin, un avocat, une professeure particulière ou un conseiller, et ce créneau disparaît aussitôt de toutes les pages, pour que personne d’autre ne le réserve. À l’heure prévue, la personne qui consulte attend, et sa ou son spécialiste, sur n’importe quelle page, la voit attendre et la fait entrer. La visite est un appel vidéo entre les deux, que personne d’autre ne peut rejoindre, et que le service qui le transporte ne peut ni voir ni entendre. À côté, les messages et les fichiers du rendez-vous, gardés par votre propre serveur, et après, les notes du ou de la spécialiste.
Cet article passe en revue chaque partie dont une vraie application a besoin, avec un exemple complet qui fonctionne : Consultations, le site de consultations privées de notre guide Next.js, où l’on réserve des spécialistes, où l’on attend dans une salle d’attente et où l’on se retrouve en vidéo. Vous pouvez l’essayer en direct : soyez Ava, dont la visite avec Lena est ouverte, et cliquez sur I'm ready. Ouvrez ensuite le site dans une fenêtre privée en tant que Lena, qui voit Ava dans la salle d’attente, et faites entrer Ava.
Ce que demande une consultation vidéo
- Une réservation qui tient. Les créneaux disponibles sur chaque page, et un créneau qui disparaît de toutes dès que quelqu’un le réserve.
- Une salle d’attente. La personne qui consulte indique qu’elle est prête, et sa ou son spécialiste la voit attendre depuis n’importe quelle page, pas seulement celle de la visite.
- Faire entrer. Seulement les deux personnes du rendez-vous, seulement autour de son heure, et la personne qui consulte une fois qu’on l’a fait entrer.
- La confidentialité. La caméra et le micro chiffrés de bout en bout, pour que les serveurs qui transportent l’appel relaient ce qu’ils ne peuvent ni voir ni entendre.
- Les messages et les fichiers à côté, gardés là où vous gardez vos données, et des notes ensuite.
- Une fin. La visite se ferme à la fin de sa demi-heure, et personne n’y revient.
Vous pouvez construire tout cela vous-même, avec WebRTC, des serveurs média et votre propre serveur websocket. Ou vous laissez un cloud temps réel gérer les médias, la salle d’attente et l’écran d’appel, pendant que votre serveur garde ce qui est vraiment à vous : les réservations, les règles et les fichiers. C’est ce que fait Consultations, avec WordPlus Cloud.
L’exemple : un site de consultations
Des spécialistes proposent des demi-heures : une médecin de famille, un avocat, une professeure particulière, un conseiller. Les personnes qui consultent réservent un créneau, ou parlent tout de suite à un ou une spécialiste disponible. Chaque rendez-vous a sa page : sa visite, ses messages et, une fois la visite terminée, les notes du ou de la spécialiste. C’est une application Next.js avec l’App Router, Better Auth et Postgres, et sa partie temps réel tient en quelques fichiers. Cinq étapes construisent ses visites.
1. Des créneaux qui disparaissent dès qu’ils sont réservés
La réservation passe par une server action. Le serveur la garde, et la base de données elle-même refuse une demi-heure déjà prise. Puis il le fait savoir par WordPlus Cloud, deux fois :
export async function bookVisit(expert: string, startsAt: number, topic: string): Promise<{ error: string }> {
const me = await currentUserId();
if (!me) return { error: 'Sign in first.' };
const booked = await book(me, expert, startsAt, topic);
if (typeof booked === 'string') return { error: booked };
await realtime.toUser(expert).emit('booked', { appointment: booked.id, name: booked.clientName, startsAt: booked.startsAt });
// Every page showing this expert's times takes this one away.
await realtime.to('experts').emit('changed');
redirect(`/appointments/${booked.id}`);
}toUser() atteint chaque page ouverte par la ou le spécialiste, sur n’importe quel appareil, avec un avis de la nouvelle visite. experts est une salle que rejoint chaque page connectée, et où seul le serveur parle. Chaque page entend changed et redemande son contenu à son serveur, sans rechargement :
useRoom('experts', { changed: () => router.refresh() });Qui regardait les créneaux de ce ou cette spécialiste voit la demi-heure disparaître. La même salle prévient les pages quand une visite commence ou se termine, si bien que la liste des spécialistes disponibles en ce moment reste juste elle aussi. WordPlus Cloud sait quelles visites sont en cours (realtime.listCalls()), et le site ne tient aucune table pour cela.
2. La salle d’attente
Chaque rendez-vous a sa salle d’attente, une salle de présence qui porte son nom :
realtime.room('waiting:{id}', { presence: true }, async (user, { id }) => mayChat(user, id));Seules les deux personnes du rendez-vous y entrent. Quand la personne qui consulte clique sur I'm ready, sa page rejoint la salle et y reste tant que la page est ouverte. La page du ou de la spécialiste lit qui s’y trouve :
function useWaiting(id: string, client: string): boolean {
return usePresence(`waiting:${id}`).some((member) => String(member.id) === client);
}La présence suit la page : fermer l’onglet suffit à quitter la salle d’attente. Et la ou le spécialiste n’a pas besoin d’être sur la page de la visite. Le serveur le signale, avec un événement waiting envoyé par realtime.toUser(), et sur n’importe quelle page un avis « Ava is in the waiting room » ouvre la visite.
3. Faire entrer
La visite est un appel de groupe qui porte le nom du rendez-vous, visit:81, déclaré une fois, par ce motif, avec sa règle :
realtime.groupCalls('visit:{id}', async (user, { id }) => mayJoinVisit(user, id), { encrypt: true });export async function mayJoinVisit(user: string | number | null, id: string): Promise<boolean> {
const appointment = await appointmentOf(id);
if (!appointment || !isParty(appointment, user) || !visitOpen(appointment)) return false;
return String(user) === appointment.expert || appointment.admittedAt !== null;
}Les deux personnes du rendez-vous et personne d’autre, tant que la visite est ouverte : de dix minutes avant son heure jusqu’à la fin de sa demi-heure, sauf si elle s’est terminée plus tôt. La ou le spécialiste entre quand la visite est ouverte, la personne qui consulte une fois qu’on l’a fait entrer. Faire entrer est une server action, qui marque le rendez-vous et prévient la personne qui consulte :
export async function letIn(id: string): Promise<boolean> {
const me = await currentUserId();
const appointment = me ? await admit(id, me) : null;
if (!appointment) return false;
await realtime.toUser(appointment.client).emit('admitted', { appointment: id, name: appointment.expertName });
return true;
}Dès lors, la règle dit oui, et la page qui attendait entre d’elle-même dans la visite. Quand une page demande à rejoindre la visite, elle s’adresse d’abord au serveur du site, à /api/realtime/calls : le même route handler que celui des appels du site de rencontre (comment mettre en place les appels vidéo dans Next.js). La règle s’exécute avec la session du site, et la page reçoit un laissez-passer pour la visite, signé avec le secret de l’application, ou la raison du refus.
4. La visite, chiffrée de bout en bout
{ encrypt: true } dans la règle chiffre la visite de bout en bout. Chaque navigateur chiffre sa caméra et son micro avec une clé que crée le serveur du site, et que reçoivent seulement ceux que la règle laisse entrer. Les clés restent sur le site et dans les deux navigateurs, et les serveurs média de WordPlus Cloud relaient ce qu’ils ne peuvent ni voir ni entendre (comment fonctionnent les serveurs média).
La visite s’affiche dans la page même du rendez-vous, là où la page la place :
<CallSpot rt={rt} call={`visit:${id}`} join={join} joinWith="microphone camera" className="h-[32rem] rounded-2xl">
{/* The waiting room as each of the two sees it, until the page joins */}
</CallSpot>Avant la visite, CallSpot affiche ce qu’il contient : la salle d’attente telle que chacune des deux personnes la voit, avec Let Ava in pour la ou le spécialiste. join fait entrer la page dans la visite, dès que la ou le spécialiste fait entrer la personne, ou dès que la personne qui attend est admise. joinWith="microphone camera" allume les deux à l’entrée, car une visite doit permettre de se voir et de s’entendre tout de suite. Sur une autre page de l’application, la visite continue dans une fenêtre dans le coin, et revient ici quand la page du rendez-vous se rouvre.
Une visite se termine aussi. La ou le spécialiste la termine avec un bouton, et le site la termine à la fin de sa demi-heure, grâce à un minuteur posé au début de la visite :
export async function endVisit(appointment: Appointment) {
await realtime.call(visitOf(appointment.id)).end().catch(() => {}); // not on, if nobody came in
await markEnded(appointment.id);
await realtime.toUser([appointment.expert, appointment.client]).emit('ended', { appointment: appointment.id });
await realtime.to('experts').emit('changed');
}end() fait sortir les deux de l’appel, la règle, voyant le rendez-vous terminé, ne laisse plus entrer personne, et les deux pages indiquent que la visite est terminée.
5. Les messages et les fichiers, puis les notes
Chaque rendez-vous a une conversation, la salle chat:{id}, pour ses deux personnes seulement, avant la visite, pendant et après. Le Chat tout prêt l’affiche à côté de la visite, avec l’indicateur de saisie, les accusés de lecture et les fichiers :
<Chat
rt={useRealtime()}
room={room}
messages={`/api/messages?room=${room}`}
upload="/api/files"
accept="image/*,.pdf,.doc,.docx,.txt"
maxFileSize={10 * 1024 * 1024}
/>Une photo, un scan ou un contrat part vers la route du site dès qu’on le choisit, et la route consulte la règle de la salle avant de le garder :
if (!me || !appointment || !(file instanceof File) || !(await realtime.mayJoin(room, me))) {
return Response.json({ error: 'This file is not for this conversation.' }, { status: 403 });
}Le site garde le fichier dans sa propre base de données et ne l’ouvre qu’aux deux personnes. WordPlus Cloud ne transporte que le message qui dit où il se trouve, scellé avec une clé que détient le site, et jamais le fichier. Le site garde aussi chaque message, depuis le webhook message, si bien que la conversation reste là pour les deux après la visite. Une fois la visite terminée, la ou le spécialiste écrit des notes, que le site garde, et la page de la personne qui consulte les affiche aussitôt, encore avec toUser().
Ce qui est inclus
- Parler tout de suite. Une personne qui consulte demande à un ou une spécialiste disponible une séance immédiate. Sa page affiche la demande, avec une minute pour l’accepter, et les deux entrent directement dans une nouvelle visite.
- Qui est disponible, à jour sur chaque page. Un ou une spécialiste en visite apparaît occupé, d’après
realtime.listCalls(), sans table de votre part. - En ligne ou non. La salle d’attente indique si la ou le spécialiste est déjà en ligne, avec
useOnline(). - Les noms et les visages de votre application. Chaque vignette et chaque message montrent un nom et une photo tels que le
profiledu site les donne, sans liste d’utilisateurs gardée dans WordPlus Cloud.
Essayez, puis construisez le vôtre
Sur la démo en direct, spécialistes et personnes qui consultent sont à vous seul pendant une journée : ce que vous y écrivez ou les personnes que vous y rencontrez, personne d’autre ne le voit, et une visite se termine d’elle-même au bout de 10 minutes. Sous chaque page, How this page works montre le code qui la fait fonctionner, étape par étape, avec les valeurs de la page que vous avez ouverte.
Le guide des consultations privées construit le site entier, avec chaque fichier de sa partie temps réel, les appels dans Next.js réunissent toutes les façons de mettre un appel dans votre application, et le chiffrement explique ce qui est scellé et qui détient chaque clé. Pour la réunion de toute une équipe plutôt que de deux personnes, lisez comment mettre en place la visioconférence dans Next.js.
WordPlus Cloud for Apps a un prix selon les connexions simultanées, à partir de 1 000, facturé au mois ou à l’année. Une visite est un appel de groupe à deux. Avec 1 000 connexions, les appels de groupe ne sont pas décomptés jusqu’à 32 personnes en vidéo à la fois, soit 16 visites, une personne en audio comptant pour moitié, et chaque tranche de 1 000 connexions en plus en ajoute 12. Au-delà, chaque personne de plus en vidéo prend un crédit par minute, et les appels entre deux personnes n’en prennent aucun. Découvrez les appels vidéo pour les applications, ou consultez les tarifs pour les applications. Des questions sur votre intégration ? Écrivez-nous.