Skip to main content
Meet est la surface d’appel, demandée avec urn:oximail:params:jmap:meet. Le partage des rôles est la première chose à comprendre : OxiMail fait autorité sur qui peut rejoindre et qui est actuellement dans un appel, et il relaie la poignée de main WebRTC entre les participants. Dans le mode pair à pair par défaut, le média ne touche jamais le serveur ; quand un relais média est branché et qu’un appel dépasse le maillage, le relais est un processus séparé et ce serveur ne porte toujours que le plan de contrôle.

L’objet de capability

Lisez la capability plutôt que d’en coder une valeur en dur : c’est là que les limites livrées sont annoncées, et trois de ses champs sont des faits d’exécution écrits dans l’objet Session pour chaque compte :
  • stunServers et turnServers sont ce qu’annonce un serveur sans configuration. Avec [meet] configuré (voir le côté exploitant), stunServers liste les URL STUN de l’exploitant et turnServers porte une entrée TURN frappée pour le compte appelant, valable une fenêtre bornée (urls, username, credential, credentialType: "password"). Le serveur n’annonce jamais le STUN d’un tiers à la place de l’utilisateur.
  • sfuAvailable et maxParticipantsSFU reflètent si un relais média a été branché au démarrage et la capacité qu’il a annoncée. Sans relais, ils restent false et null, et ce sont alors des faits, pas des réservations.
  • reactionEmoji est le vocabulaire fermé que MeetSignal/reaction accepte, dans l’ordre d’affichage recommandé. Une réaction atteint tous les participants actifs, donc le serveur accepte ces valeurs et rien d’autre.
  • recordingAvailable vaut false, et config.recordingAllowed est refusé par invalidProperties : le serveur ne peut enregistrer que le média qu’il voit, et dans le maillage il n’en voit aucun.

Les objets

Une MeetSession porte id, accountId, exactement l’un de chatRoomId ou calendarEventId (l’ancre, voir plus bas), un conversationRoomId optionnel (où vit le chat de l’appel, une fois que quelqu’un a écrit), mode (p2p, ou sfu après bascule), mediaEndpoint (présent seulement en mode sfu), config, participants, created, et les optionnels started et ended. participants est posé par le serveur : il est lu du magasin et jamais pris d’un client. started est estampillé au moment où le second participant est actif, c’est-à-dire quand une session devient un appel plutôt qu’une invitation ; quelqu’un qui attend dans la salle d’attente n’a rien commencé, la réunion débute quand on le laisse entrer. config porte audioOnly, maxParticipants, waitingRoom et recordingAllowed. audioOnly force la vidéo de chaque participant à l’arrêt à l’entrée. maxParticipants est le plafond de l’organisateur, et il est appliqué (voir les places). waitingRoom ouvre la file décrite plus bas. recordingAllowed est refusé. Un MeetParticipant est plat sur le fil : id, exactement l’un de accountId ou guestId, deviceId, displayName, un role de moderator ou participant, un status parmi active, waiting, left ou kicked, les drapeaux média audioEnabled, videoEnabled, screenSharing, et handRaised avec un handRaisedAt posé par le serveur tant que la main est levée. id est le seul identifiant que le fil possède pour « quelqu’un d’autre », et le seul qui puisse nommer un invité ; les verbes du modérateur le prennent en participantId.

Les méthodes

Une seule réponse pour les étrangers, partout : un appelant sans qualité dans une session (ni membre du salon, ni participant, ni invité) reçoit notFound avec le même libellé qu’un identifiant inventé, que la session soit vivante ou terminée. Seul un appelant qui sait de façon démontrable que la session existe se voit répondre le refus plus fin (forbidden : pas modérateur, pas actif, session terminée, retiré par un modérateur).

Une session est ancrée, et c’est ainsi qu’elle sonne

Une session pend d’exactement une de deux choses, et l’ancre est ce sur quoi l’autorisation se décide :
  • Un salon de discussion (chatRoomId) : les membres du salon peuvent rejoindre. La création diffuse un StateChanged pour la collection MeetSession à tous les membres du salon, et c’est ainsi qu’un appelé apprend qu’un appel existe sans type de notification dédié. La trame porte un état, pas un identifiant, donc la seconde moitié de la boucle est MeetSession/get avec ids: null (RFC 8620 §5.1 : tous les enregistrements que l’appelant peut voir), qui répond avec les sessions actives des salons de l’appelant, les réunions en cours qu’il organise, et les sessions où il a été invité. Ces deux appels forment la boucle de découverte des appels entrants ; un client n’a besoin de rien d’autre pour sonner.
  • Une réunion planifiée (calendarEventId) : les participants internes de l’événement peuvent rejoindre, et chacun d’eux modère, pour qu’un invité externe ne reste jamais coincé dans une file que seul un organisateur nommé pourrait libérer. L’identifiant peut être la copie iTIP de l’appelant ; l’ancre suit le uid jusqu’à la copie de l’organisateur, si bien que deux participants n’ouvrent jamais deux appels pour une réunion. La session d’une réunion planifiée naît paresseusement, à la première arrivée, et cette arrivée peut être un invité qui suit le lien avant que le praticien ait rejoint. Un participant atteint l’appel par l’événement lui-même : MeetSession/create avec calendarEventId est idempotent et rend la session vivante, ce qui explique que ids: null liste les réunions en cours que l’appelant organise mais pas toutes celles auxquelles il assiste. Voir les réunions planifiées.
Une session ancrée sur un salon converse dans ce salon. Une session ancrée sur une réunion n’a pas de salon tant que personne n’écrit dans le chat de l’appel ; à ce moment un salon est créé et enregistré en conversationRoomId. Faire venir quelqu’un dans un appel déjà commencé. MeetSession/invitePrincipals {sessionId, principalIds} donne aux principaux nommés du tenant le droit de rejoindre cette session, et rien d’autre : le salon ne change jamais (la composition d’un salon direct est immuable, et ajouter quelqu’un à un salon pour un avis de dix minutes en aurait fait un membre pour toujours), le droit meurt avec la session, et réinviter quelqu’un qui a manqué l’appel fait sonner son appareil à nouveau. Tout participant actif peut inviter, pas seulement un modérateur : la présence dans l’appel est la qualité, et le modérateur garde le dernier mot par kick et la salle d’attente. Chaque identifiant doit être valide, nommer un principal du tenant et résoudre vers un compte, sinon l’appel entier est refusé. Inviter n’est pas rejoindre : la place se prend toujours au join, sous le même plafond que tout le monde.

Les places et le plafond

Le nombre de places actives qu’une session peut tenir est min(config.maxParticipants, la limite du mode) : maxParticipantsP2P (3) dans le maillage, la capacité du relais figée à la bascule en mode sfu. Le plafond de l’organisateur fait loi sous la limite du mode (une téléconsultation plafonnée à 2 refuse la troisième personne même si le maillage pourrait la porter) et il est écrêté, pas honoré, au-dessus. Un plafond de 0 ou 1 est refusé à la création, puisque le créateur prend la première place. Le plafond s’applique à toutes les façons de prendre une place : une entrée neuve, un appareil parti qui revient, et une admission depuis la salle d’attente. Quelqu’un qui attend ne consomme aucune place. Une place refusée répond tooManyEntries avec une description qui nomme la limite qui a joué, maxParticipants (« l’organisateur l’a choisi ») ou le plafond du mode (« la technologie l’impose »). Au plafond du maillage avec un relais branché, la place s’achète en basculant la session au lieu d’être refusée.

La salle d’attente, et les verbes du modérateur

Avec config.waitingRoom: true, un join place l’appelant en waiting et diffuse le changement pour que le modérateur voie la file grandir. Un modérateur n’attend jamais, même sur un second appareil : le rôle est porté par le compte, donc le créateur qui rejoint depuis son téléphone est toujours le modérateur. Renvoyer join en attendant laisse exactement là où on est, et un participant actif qui rejoint à nouveau n’est jamais rétrogradé dans la file. Un participant en attente bat le cœur avec MeetSession/refresh comme un participant actif et est balayé par la même ronde si son onglet meurt. Les verbes du modérateur prennent tous sessionId et participantId (sauf muteAll), partagent un même préambule (résoudre la session, prouver que l’appelant la modère, résoudre la cible, refuser une cible déjà partie) et sont cinq méthodes distinctes plutôt qu’un moderate à argument d’action, pour que la garde de lecture seule et tout contrôle de capacité lisent le verbe au lieu d’analyser un argument.
  • admit rend actif un participant en attente, sous le plafond de places, et estampille started si c’est le second participant actif. reject l’envoie en left et retire son bail.
  • mute et muteAll sont un ordre, pas une demande : le serveur écrit audioEnabled: false sur la cible immédiatement et le diffuse, si bien que la vue qu’a la salle de qui est muet ne dépend jamais d’un client bloqué ou lent. Le participant coupé peut se réactiver ensuite avec MeetParticipant/update : une coupure du modérateur est une interruption, pas un verrou, et rien dans ce produit n’a besoin d’une primitive de censure.
  • kick pose kicked (jamais left : l’un est une décision prise à votre sujet), estampille leftAt, retire le bail, et est appliqué à la porte : MeetSession/join refuse tout compte qui porte une ligne kicked dans la session, donc la même personne ne peut pas revenir depuis un second navigateur. La signalisation s’arrête d’elle-même, puisque chaque MeetSignal/* exige les deux bouts actifs.
  • readmit lève l’exclusion sans téléporter personne : la cible arrive en left, rejoint normalement, refait la queue là où une salle d’attente est configurée, et garde son leftAt d’origine. Ce verbe existe parce que reject est à côté d’admit dans une file qui bouge pendant que le modérateur la lit, et que le seul remède à une erreur de clic était de terminer la réunion.
MeetSession/end reste un verbe de modérateur ; terminer estampille ended, règle chaque participant et retire chaque bail, sans réécrire l’histoire : une ligne kicked reste kicked et chaque leftAt garde l’instant où cela s’est produit, si bien qu’une exclusion à 14 h 02 dans une réunion terminée à 14 h 30 est toujours une exclusion à 14 h 02.

Les invités : rejoindre par lien, sans compte

Une personne externe rejoint un appel par un lien d’invitation, échangé contre un identifiant d’invité. Ce sont deux objets différents, aux préfixes différents, et aucun n’est jamais un compte. Le lien (MeetSession/invite {sessionId, expiresIn?}, modérateur seulement) répond inviteId, token, url et expiresAt. Le jeton (mi_…, environ 190 bits d’entropie) apparaît dans cette réponse et nulle part ailleurs : seule son empreinte est stockée, donc aucune lecture ultérieure ne peut retrouver un lien vivant. expiresIn vaut 24 heures par défaut et est refusé au-delà de 7 jours, puisqu’un lien plus long est une clé de porte laissée sans surveillance et que le modérateur peut toujours en frapper un neuf. Un lien sert N invités : une téléconsultation avec deux participants externes est un lien transféré deux fois. L’url est {origine web}/meet/join/{token}, une page côté client. MeetSession/revokeInvite {inviteId} tue un lien avant son expiration sans terminer la réunion et sans toucher aux invités qui l’ont déjà échangé ; évincer une personne est kick, une autre décision sur un autre sujet. Frapper un lien n’est pas une mutation de MeetSession et ne diffuse rien. L’échange est POST /meet/guest {token, displayName}, non authentifié par nature. Ses défenses, dans l’ordre : un budget par jeton de 10 échanges par minute avant toute lecture (la moitié par IP est portée par le limiteur HTTP global) ; une seule réponse pour tout échec, 404 {"error": "invalid or expired invitation"} pour un jeton inconnu, révoqué ou expiré, une session disparue ou terminée, si bien qu’un sondeur n’apprend rien sur le contrôle qui a refusé ; et un nom d’affichage borné (128 octets, aucun caractère de contrôle) parce qu’il atterrit sur la liste de chaque membre. Un échange réussi répond :
  • L’invité naît waiting, quoi que dise le waitingRoom de la session, ne prend aucune place, et le modérateur le voit arriver par la diffusion ordinaire. Sur une réunion planifiée, l’invité peut être la première personne à se présenter : la session est ouverte dans la même transaction, si bien qu’un patient qui clique avant le praticien arrive dans une salle d’attente qui existe.
  • L’ICE voyage dans l’amorce : les invités sont particulièrement souvent derrière des NAT hostiles, donc la réponse porte la liste STUN et un identifiant TURN par invité, à durée limitée, frappé comme pour la capability des membres.
  • L’identifiant (mg_…, 12 heures) ouvre le point d’entrée JMAP et le WebSocket. Son confinement est structurel : une liste fermée de méthodes est consultée en fermé sur échec, donc une méthode ajoutée n’importe où dans le serveur est inaccessible à un invité tant que personne ne l’y met consciemment. Un invité peut appeler MeetSession/get, /refresh, /leave, /chatSend, /chat, MeetParticipant/update et les six MeetSignal/* ; il ne frappe rien, ne modère rien, et il n’y a pas de join, puisqu’il a été placé en waiting à l’échange et qu’il n’existe pas de seconde porte.
  • Une vue réduite pendant l’attente. MeetSession/get pour un invité voit exactement une session ; tant que l’invité est waiting, il reçoit une projection expurgée, sa propre ligne seulement, jamais la liste des participants : qui assiste à une réunion est une donnée, et un inconnu non admis qui tient un lien transféré est le dernier appelant qui devrait la lire. Un invité n’a pas de curseur de changements, state est la chaîne fixe "guest", et le client invité interroge get au lieu de synchroniser.
  • Le deviceId de l’invité est opaque et choisi par le serveur. Un membre adresse un invité dans MeetSignal/* en mettant le guestId du participant dans toAccountId ; les deux espaces d’identifiants ne peuvent pas entrer en collision, et rien d’autre ne change sur le fil.

Les réunions planifiées, et le lien dans l’invitation

Créer un événement d’agenda « avec visio » est un seul geste : CalendarEvent/set accepte une instruction meetLink sur create et sur update, seulement quand la requête déclare la capability meet, et la refuse sinon même pour false, parce qu’un client qui nomme la propriété sans la capability a déjà des attentes fausses sur cet événement. meetLink: true prend la durée par défaut du serveur ; meetLink: {expiresIn: <secondes>} en énonce une (365 jours par défaut, deux ans au plus), parce que la durée de validité d’un lien de réunion est la connaissance de l’organisateur, pas celle du serveur. Le serveur frappe le lien à la création et l’écrit dans virtualLocations sous sa propre clé fixe meet ({uri, features: {video, audio}}, RFC 8984 §4.2.6) ; les autres entrées du client survivent, et un client ne peut pas pré-remplir cette clé avec une URL de son choix. L’uri part ensuite en CONFERENCE dans l’invitation iTIP sortante et arrive chez un invité sur Outlook ou Apple Calendar comme un lien pour rejoindre, et la copie du participant porte aussi virtualLocations. Sur update, la même instruction ajoute un appel à une réunion qui existe déjà ; elle fait avancer la séquence, si bien qu’un REQUEST mis à jour part avec la porte dedans et que les réponses déjà reçues survivent. Un meetLink enfoui dans une occurrence modifiée est refusé en entier, en nommant l’occurrence : une série porte une salle, et la visio s’ajoute à l’événement, jamais à l’une de ses occurrences. La session derrière un tel lien naît à la première arrivée, avec la MeetConfig par défaut ; la présence d’un invité force la salle d’attente quoi qu’il en soit.

La présence est un bail, pas un drapeau

Rejoindre prend un bail indexé par (compte, appareil). Il expire 30 secondes après le dernier join ou refresh, et MeetSession/refresh répond cette fenêtre en expiresIn pour qu’un client puisse interroger confortablement à l’intérieur plutôt que de deviner. Envoyer un signal compte aussi comme preuve de vie et touche le bail de l’émetteur. Créer une session touche le bail du créateur, si bien qu’un créateur qui plante avant son premier join est balayé comme n’importe qui. Quand un bail expire, une ronde passe ce participant en left dans le magasin et diffuse le changement ; quand plus personne n’est actif, la session se termine d’elle-même. C’est pourquoi un onglet de navigateur qui disparaît ne laisse pas de fantôme dans l’appel, ni dans la salle d’attente. Les baux vivent en mémoire et ne sont jamais persistés. Un redémarrage du serveur les perd donc tous, et la présence se reconstruit à partir des join/refresh frais qui la rétablissent : la même sémantique que la présence du chat, où tout le monde repart hors ligne après un redémarrage.

L’état média et la main levée

MeetParticipant/update {sessionId, deviceId, audioEnabled?, videoEnabled?, screenSharing?, handRaised?} publie l’état propre de l’appelant ; un champ absent est laissé tel quel. C’est une méthode à part plutôt que des arguments sur refresh, parce que l’état média doit se diffuser et que le battement de cœur ne le fait délibérément pas, et parce qu’un refresh raté et une coupure ratée ne veulent pas dire la même chose. La main levée est un état, pas un événement : une main levée pendant que le modérateur regarde ailleurs est toujours levée quand il revient, un retardataire voit les mains déjà levées, et handRaisedAt permet à un client d’ordonner les tours de parole par l’instant où chaque main s’est levée. Le jumeau éphémère, une réaction, est un signal.

La signalisation est relayée, jamais stockée

Les quatre méthodes MeetSignal/* prennent sessionId, toAccountId, toDeviceId et fromDeviceId, plus la charge du signal (sdp, candidate, reason), transmise telle quelle et jamais lue.
  • Rien n’est persisté, et rien n’est journalisé. Il n’y a ni boîte de signalisation, ni file, ni nouvelle tentative : le relais est transitoire par contrat. Un refus journalise la session et le type de signal, jamais le contenu.
  • delivered est un accusé sur les connexions, pas sur les personnes. true signifie qu’au moins un WebSocket ouvert du destinataire a reçu la trame. false signifie qu’il n’a aucune connexion ouverte en ce moment : ni une erreur, ni une mise en file pour plus tard. Traitez-le dans l’interface (« semble hors ligne »), pas comme un échec à réessayer.
  • Le serveur possède les champs d’identité de la trame. type, signal et fromAccountId sont posés après que la charge de l’appelant a été étalée dans la trame, si bien qu’un appelant ne peut pas les écraser en mettant ces clés dans sa propre charge.
  • Les deux bouts doivent être des participants actifs. L’émetteur est vérifié par (accountId, fromDeviceId) et le destinataire par toAccountId/toDeviceId (un identifiant de compte ou un guestId) ; tout le reste est forbidden, et tout échec de résolution des participants relaie vers personne plutôt que vers tout le monde.
  • Limité par (tenant, compte, session) à 120 signaux par minute par défaut, dimensionné pour l’ICE au fil de l’eau, environ deux par seconde. Au-delà, l’erreur de limite de la RFC 8620 §3.6.1.
MeetSignal/reaction {sessionId, fromDeviceId, emoji} est la seule trame diffusée de la famille : chaque champ est nommé et validé (pas de charge opaque, puisqu’elle va à toute la salle), emoji doit être l’une des valeurs de reactionEmoji de la capability, et tout le reste est invalidProperties, jamais un abandon silencieux ni une valeur de remplacement.

Le chat de l’appel

MeetSession/chatSend {sessionId, text} écrit dans la conversation de l’appel, invités compris. Ce n’est pas ChatMessage/set avec l’invité laissé passer : ce gestionnaire résout son auteur depuis un compte et prouve l’appartenance par une ligne de salon, et un invité n’a ni l’un ni l’autre ; la porte des invités est un verbe distinct dont l’autorité vient d’être un participant admis de la session nommée (un invité en salle d’attente ne peut pas écrire). Il n’accepte que text, 8 Kio au plus, si bien qu’aucune marque de lien ne peut naître sur le chemin invité ; l’enveloppe stockée et la diffusion par compte sont celles du chat, et les membres lisent le message dans leur salon comme n’importe quel autre. Le salon d’un appel ancré sur une réunion est créé au premier message. MeetSession/chat {sessionId, limit?} lit la conversation bornée à la présence de l’appelant : un invité lit depuis sa propre arrivée, jamais avant (ce qui a été dit avant qu’on le laisse entrer n’est pas à lui, et un lien transféré ne doit pas livrer la conversation qui l’a précédé), et la fenêtre se ferme quand il part. Les membres sont servis par le même verbe sur la même règle, ce qui rend « ce que l’invité peut voir » vérifiable en le demandant au serveur ; un membre qui veut l’historique complet du salon a la requête générale du chat. La réponse porte roomId (null et une list vide tant que personne n’a parlé), windowFrom, windowTo et des entrées avec authorPrincipalId ou authorGuestName, sentAt et text ; la page par défaut est de 100.

Au-delà du maillage

Un appel commence pair à pair et y reste jusqu’à maxParticipantsP2P. Avec un relais média branché, une entrée ou une admission qui dépasserait le maillage bascule la session vivante en mode: "sfu" : le relais alloue la conférence, la session gagne mediaEndpoint (le point d’entrée du relais, typiquement une URL de signalisation wss://, porté tel quel et jamais analysé par ce serveur), la capacité du relais est figée sur la session, et chaque participant apprend la bascule par la diffusion StateChanged ordinaire et renégocie contre le point d’entrée. Si le relais refuse l’allocation, la session reste p2p et l’entrée déclenchante est refusée exactement comme sans relais : l’appel en cours n’est jamais coupé. Il n’y a pas de retour : un appel qui a dépassé le maillage une fois ne ferait que rebondir entre les deux topologies à chaque départ et arrivée si on l’autorisait à revenir. Le relais est un processus séparé (il analyse du média hostile avant authentification venant de pairs anonymes, donc il ne partage jamais d’espace d’adressage avec le serveur de courrier) branché par le seam d’extension du serveur ; le chemin média va du pair au relais directement, et ce serveur ne voit que des appels de cycle de vie, session ouverte, arrivée, départ, fermeture, faits depuis les gestionnaires dans l’ordre exact où ils ont été validés. Sans relais branché, rien ne change : le binaire communautaire n’en a aucun, se comporte bit pour bit comme la phase « maillage seul », et la capability le dit. Le même seam permet à un relais de rendre un artefact de fin d’appel (un enregistrement, un résumé écrit par une machine) sous forme de blob appartenant au compte de la session et conservé par une ligne de référence ; rien dans le binaire communautaire n’en produit.

Le côté exploitant

  • Vide par défaut, à dessein. Un serveur non configuré n’annonce ni STUN ni TURN plutôt que d’emprunter ceux d’un tiers, ce qui livrerait l’adresse IP de chaque appelant à quelqu’un qui n’est pas l’exploitant. Le déploiement recommandé est un coturn sur la même box, pour les deux rôles. turn_urls sans turn_shared_secret_file refuse de démarrer : cela annoncerait un relais contre lequel aucun client ne peut s’authentifier. Un turn_shared_secret écrit en ligne est refusé aussi : quiconque lit le TOML pourrait fabriquer des identifiants de relais valides pour n’importe quel compte, donc le secret vit dans un sidecar 0600 (voir les secrets hors du TOML). Les identifiants remis aux clients sont dérivés du secret par compte et expirent.
  • STUN intégré. stun_bind lie une socket UDP et répond aux requêtes Binding de la RFC 5389 depuis le serveur lui-même, Binding seulement, jamais un relais ; un datagramme malformé n’obtient aucune réponse (posture anti-amplification) et chaque IP source est limitée par stun_rate_per_minute. Lier une socket n’annonce rien par elle-même : listez aussi l’URL stun: publique dans stun_servers, et le journal de démarrage le dit.
  • Rétention. Les lignes d’un appel terminé disent qui a assisté à quelle réunion, quand, et depuis quel appareil, et rien ne les supprimait avant ce réglage ; elles sont balayées retention_days après la fin. Le magasin porte une colonne par tenant que le balayage lit (meet_retention_days, pour un métier qui doit conserver davantage), mais aucun verbe ni méthode ne l’écrit encore, si bien que la valeur configurée est ce que chaque tenant reçoit aujourd’hui. Les lignes Meet font aussi partie de ce qu’un effacement de compte retire.
Deux préférences côté compte vivent dans la section meet de Preferences : waitingRoom (une politique de compte que le client lit et met dans config.waitingRoom quand il lance un appel) et devices, une carte indexée par installation portant cameraId/microphoneId, puisqu’un choix de webcam n’a de sens que sur la machine qui porte la webcam. Une clé inconnue dans la section est refusée et nommée.

Ce qui n’y est pas, délibérément

  • Pas d’enregistrement : refusé à config.recordingAllowed, recordingAvailable: false.
  • Pas de transfert de modération : sur une ancre salon le créateur est le modérateur ; sur une ancre réunion chaque participant interne l’est. Effacer le compte du créateur supprime la session entière plutôt que d’en laisser une inadministrable.
  • Pas de mode groupe sans relais, et pas de retour au maillage une fois basculé.
  • Pas de seconde porte pour les invités : un invité est placé en waiting à l’échange, ne peut ni join, ni modérer, ni frapper un lien.
La surface de chat sur laquelle ces sessions s’ancrent est documentée sur la page du chat ; le côté agenda d’une réunion planifiée sur la page de l’agenda ; la session et la mécanique de push dont elles dépendent sont sur JMAP Core.