urn:oximail:params:jmap:chat, avec polls, custom-emoji et mentions comme objets de capacité voisins) : le même modèle de requête, les mêmes chaînes d’état et le même push que tous les autres domaines. Un client JMAP obtient le chat avec la mécanique qu’il a déjà. Le temps réel passe par le WebSocket JMAP.
Objets et méthodes
Les pièces jointes sont des blobs JMAP standard ; le contenu partagé en salon est stocké à l’échelle de l’organisation pour que chaque membre le résolve.
Transport et vivacité
- WebSocket (
/jmap/ws) : appels de méthode et push sur une seule socket. Les indicateurs de saisie sont des trames WS hors du modèle de requête JMAP, limitées aux seuls membres du salon et bridées à une trame par 3 s par (salon, compte) ; les arrêts passent toujours pour que les indicateurs s’effacent. - La vivacité est revérifiée. L’ancre d’authentification de la socket (la session d’appareil) est revalidée périodiquement ; une session révoquée ou expirée ferme en
4401et le client se reconnecte proprement, au lieu de traîner à moitié mort (le chat coule, les requêtes HTTP échouent). La rotation de routine des tokens ne déconnecte jamais un client sain.
Plafonds de capacité
L’objet de capacité du chat annonce les quotas appliqués comme données :{maxRoomsPerAccount: 100, maxParticipantsPerRoom: 200, maxMessageSizeOctets: 65536, maxPinnedPerRoom: 50, maxReadReceiptLiveMembers: 20}, pour que les clients se bornent avant de rencontrer overQuota/tooLarge (multi-tenant). maxPinnedPerRoom plafonne les messages épinglés par salon ; la taille de message borne l’objet de création sérialisé (content et encryptedContent uniformément) ; maxReadReceiptLiveMembers est expliqué sous les accusés de lecture.
Les accusés de lecture
ChatReadReceipt est un curseur de lecture par membre, à savoir l’identifiant du dernier message que ce membre a lu. C’est ce qu’un client affiche sous forme d’une pile d’avatars de lecteurs sous un message.
- Un curseur n’avance jamais que vers l’avant.
ChatReadReceipt/setrefuse unlastReadMessageIdexplicite plus ancien que celui déjà stocké, avecinvalidProperties, avant toute écriture : une entrée refusée laisse donc intacts le curseur comme le badge de non-lus. Pour remettre un salon en non-lu, utilisezdestroy: c’est le sens prévu. Quand le curseur a été déduit par défaut au lieu d’être envoyé par l’appelant, ce qui arrive tout seul lorsque le message le plus récent est détruit, ce n’est jamais une erreur : le curseur stocké l’emporte, et la réponse rapporte le curseur que le serveur détient, pas le plus ancien qu’on lui a passé. - Une lecture prévient les autres membres, en direct. Marquer un salon comme lu met en file un changement d’état
ChatReadReceiptpour chacun des autres membres, afin que leur surface « vu par » suive la lecture, au lieu d’être juste au chargement puis figée. Deux bornes s’appliquent : un canal qui garde ses curseurs cachés ne prévient personne, et un salon plus grand quemaxReadReceiptLiveMembers(20 par défaut) ne prévient personne non plus, car une lecture prévient tous les autres membres et le volume croît donc comme le carré de la taille du salon. Au-delà de ce plafond, les curseurs restent exacts etChatReadReceipt/getles renvoie toujours tous : ils se rafraîchissent simplement à la prochaine récupération plutôt que dans la seconde. - Les salons directs et de groupe renvoient l’accusé de chaque membre. Un canal non, sauf s’il le choisit. Le raisonnement derrière la fermeture des curseurs d’un canal, c’est qu’un grand salon de diffusion laisserait fuiter des habitudes de lecture : vrai là, et faux pour le petit canal d’équipe où « qui a vu ce message » est justement la raison de poster.
ChatRoomporte doncreadReceiptsVisible:falsepar défaut, pour que la confidentialité reste le défaut et que les salons existants gardent leur comportement, réglable par un propriétaire uniquement, et propriété partagée du salon plutôt que préférence par membre, puisqu’elle décide de ce que le salon divulgue de ses membres et qu’un membre ne peut pas y répondre pour les autres. La poser sur un salon direct ou de groupe est refusé au lieu d’être stocké : ces salons renvoient déjà tous les accusés, et une valeur stockée là serait une promesse que rien ne tient.
Envoi programmé et aperçus de liens
Deux capacités voisines annoncent des surfaces d’extension de ChatMessage :urn:oximail:params:jmap:scheduled-send: une créationChatMessage/setpeut porterscheduledAt; le message attend avec unsendStatusjusqu’à ce que le planificateur côté serveur le délivre (le même planificateur que les alertes d’agenda).urn:oximail:params:jmap:link-unfurl: le serveur remplit les aperçus de liens sur les messages (récupérés côté serveur derrière une garde anti-SSRF ; les clients ne récupèrent jamais eux-mêmes des URL tierces).
ChatMessage.unfurls est un tableau d’objets typés, complété au fur et à mesure que chaque URL du corps se résout, et absent tant qu’un message n’a pas d’URL ou qu’aucun aperçu n’a encore abouti :
url sont facultatifs et omis quand la page source ne les fournissait pas. Le stockage reste un blob JSON dans une colonne, mais c’est désormais un détail d’implémentation que le fil ne laisse plus filtrer : cette propriété était auparavant projetée telle que la colonne, soit un tableau d’octets bruts ([91, 123, 34, …]), ce qui coûtait environ quatre caractères sur le fil par octet utile et obligeait chaque client à réimplémenter octets vers UTF-8 puis analyse JSON, pour une donnée que le serveur produit et comprend entièrement. Le décodage a maintenant lieu une seule fois, côté serveur, là où un blob impossible à analyser échoue bruyamment au lieu d’être silencieusement remplacé par une liste vide.
Le contraste avec
encryptedContent est volontaire : celui-là est un blob par conception (la trajectoire du chiffrement de bout en bout) et opaque au serveur. unfurls ne l’a jamais été.Comportements à connaître
- La diffusion est matérialisée : un message de salon écrit une ligne par membre, ce que les plafonds bornent, et c’est pourquoi les écritures d’appartenance, de message et de journal de changements sont transactionnelles (
/changesne peut jamais manquer un message validé). - Les mentions sont structurelles (
@principalcomme donnée, pas un balayage de chaîne) : un renommage ne les casse pas et les clients les rendent fiablement. - Les éditions de messages gardent l’historique ; les références de l’historique d’édition sont suivies par le GC de blobs, une pièce jointe éditée ne devient jamais une référence pendante.
- Les messages différés et les rappels passent par le même ordonnanceur côté serveur que les alertes d’agenda.