> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Meet

> Les appels en temps réel sur JMAP : une MeetSession ancrée sur un salon de discussion, le bail de présence qui définit qui est réellement dans l'appel, et le relais transitoire MeetSignal qui transporte une poignée de main WebRTC sans jamais la stocker.

Meet est la surface d'appel, demandée avec `urn:oximail:params:jmap:meet`. La répartition des rôles est la première chose à comprendre : OxiMail fait autorité sur **qui peut rejoindre et qui est présent** dans un appel, et il **relaie** la poignée de main WebRTC entre les participants. Le média lui-même ne touche jamais le serveur.

## L'objet de capacité

Lisez la capacité au lieu d'en coder quoi que ce soit en dur : c'est là que les limites livrées sont annoncées.

```json theme={null}
"urn:oximail:params:jmap:meet": {
  "maxParticipantsP2P": 3,
  "maxParticipantsSFU": null,
  "sfuAvailable": false,
  "recordingAvailable": false,
  "stunServers": [],
  "turnServers": null
}
```

`stunServers: []` et `turnServers: null` sont honnêtes plutôt que vides par omission : le serveur ne fournit aucun serveur ICE. Apportez les vôtres, et attendez-vous à ce que le pair-à-pair échoue entre deux NAT symétriques sans relais TURN.

## Objets et méthodes

| Méthode                                                | Ce qu'elle fait                                                                                                                                                |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MeetSession/create`                                   | Ouvre une session sur un salon. `chatRoomId` est obligatoire.                                                                                                  |
| `MeetSession/get`                                      | La lecture standard. Avec `ids: null`, elle renvoie les sessions **actives** de tous les salons dont vous êtes membre : voir la boucle de découverte plus bas. |
| `MeetSession/join`                                     | Rejoint en tant que `(accountId, deviceId)` et prend un bail de présence.                                                                                      |
| `MeetSession/refresh`                                  | Renouvelle le bail et répond `expiresIn`.                                                                                                                      |
| `MeetSession/leave`                                    | Quitte explicitement.                                                                                                                                          |
| `MeetSession/end`                                      | Termine la session pour tout le monde.                                                                                                                         |
| `MeetSignal/offer`, `/answer`, `/candidate`, `/failed` | Relaient une trame de signalisation vers l'appareil d'un participant.                                                                                          |

Une `MeetSession` porte `id`, `accountId`, `chatRoomId`, un `calendarEventId` optionnel, `mode`, `config`, `participants`, `created`, ainsi que `started` et `ended` optionnels. La liste `participants` est **posée par le serveur** : elle est chargée depuis le magasin et jamais prise d'un client. La date `started` est frappée au moment où le *deuxième* participant rejoint, c'est-à-dire quand une session devient un appel et non plus une invitation.

Un participant est plat sur le fil (`accountId`, `deviceId`, `displayName`), avec un `role` valant `moderator` ou `participant`, et un `status` que la phase 1 ne met jamais ailleurs qu'à `active` ou `left`.

## Une session est ancrée sur un salon

`chatRoomId` est obligatoire, et c'est sur lui que porte l'autorisation : vous pouvez rejoindre une session si vous êtes membre de son salon. Il n'y a aucune liste d'invités séparée à tenir synchronisée avec le salon.

La création diffuse un `StateChanged` de la collection `MeetSession` à **tous les membres du salon** : c'est ainsi qu'un appelé apprend qu'un appel existe, sans type de notification dédié. Cette trame porte un état, pas un identifiant. La seconde moitié de la boucle est donc `MeetSession/get` avec `ids: null` (RFC 8620 §5.1 : tous les enregistrements que l'appelant peut voir), qui répond avec les sessions actives de ses salons. Ces deux appels forment la boucle de découverte d'appel entrant ; un client n'a besoin de rien d'autre pour sonner.

## L'adhésion 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 sonde confortablement à l'intérieur au lieu de la deviner. Envoyer un signal vaut également preuve de vie et rafraîchit le bail de l'émetteur.

Quand un bail expire, un balayeur bascule 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 pour cela qu'un onglet de navigateur qui disparaît ne laisse pas de fantôme dans l'appel.

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 re-dérive 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.

## La signalisation est relayée, jamais stockée

Les quatre méthodes `MeetSignal/*` prennent `sessionId`, `toAccountId`, `toDeviceId` et `fromDeviceId`, plus la charge utile 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 réémission : le relais est transitoire par contrat. Un refus journalise la session et le type de signal, jamais le contenu.
* **`delivered` est un accusé sur des connexions, pas sur des personnes.** `true` veut dire qu'au moins une WebSocket ouverte du destinataire a reçu la trame. `false` veut dire qu'il n'a aucune connexion ouverte à cet instant : ce n'est pas une erreur, et ce n'est pas mis en attente. Traitez-le dans votre interface (« semble hors ligne »), n'y voyez pas un échec à réessayer.
* **Le serveur possède les champs d'identité de la trame.** `type`, `signal` et `fromAccountId` sont apposés *après* l'étalement de la charge utile de l'appelant, si bien qu'un appelant ne peut pas les écraser en plaçant ces clés dans sa propre charge.
* **Les deux extrémités doivent être des participants actifs.** L'émetteur est contrôlé par `(accountId, fromDeviceId)` et le destinataire par `toAccountId`/`toDeviceId` ; tout le reste est `forbidden`, et un échec de résolution des participants relaie à personne plutôt qu'à tout le monde.
* **Débit limité par `(tenant, compte, session)`** à 120 signaux par minute par défaut, une taille pensée pour le trickle ICE, environ deux par seconde. Au-delà du budget, l'erreur de limite de la RFC 8620 §3.6.1 répond.

## Ce que la phase 1 ne fait pas

Les restrictions ci-dessous sont un périmètre délibéré, pas des défauts, et chacune s'observe depuis l'objet de capacité ou par une erreur nommée plutôt que par essais :

* **Pair-à-pair uniquement.** `mode` est figé à `"p2p"`, `sfuAvailable` vaut `false`, et rejoindre au-delà de trois participants actifs répond `tooManyEntries`. Il n'existe aucun mode groupe en repli.
* **Ni enregistrement, ni salle d'attente.** `recordingAllowed` et `waitingRoom` existent dans la forme de `MeetConfig` mais sont refusés en `invalidProperties`.
* **Pas d'invités.** L'identité d'un participant est membre *ou* invité au niveau du schéma, et aucun chemin de phase 1 ne crée d'invité.
* **Aucune méthode `MeetParticipant/*`** : ni admission, ni refus, ni exclusion, ni coupure de micro. Les participants se lisent à travers la session.
* **Un seul modérateur, et c'est le créateur.** La modération ne se transfère pas.

Ce dernier point a une conséquence qui mérite d'être dite : puisqu'une session ne peut pas survivre à son modérateur sous une forme administrable, effacer le compte du créateur supprime la session entière au lieu de laisser derrière elle une session que personne ne peut administrer. Les lignes Meet font partie de ce qu'un effacement de compte retire.

La surface de discussion sur laquelle ces sessions s'ancrent est documentée sur la [page chat](./jmap-chat) ; les mécaniques de session et de push dont elles dépendent sont sur [JMAP Core](./jmap-core).
