urn:ietf:params:jmap:contacts), avec des fiches en objets JSContact (RFC 9553). Les mêmes données sont servies en CardDAV.
Objets et méthodes
Comportements à connaître
- Le serveur possède l’
uid. Unuidfourni par le client à la création est rejeté (invalidProperties) : c’est le serveur qui le frappe, ce qui rend la correspondance CardDAV sans ambiguïté. - Une propriété inconnue est refusée nommément, à la création comme à la modification. Une fiche envoyée avec un prénom et un nom séparés, ce qu’un logiciel venu d’ailleurs fait naturellement, répondait succès et existait sans nom : seules les propriétés reconnues avaient été retenues, les autres jamais regardées. Les propriétés que le serveur gère lui-même (les statistiques d’échange) restent refusées par leur contrôle dédié, qui dit pourquoi ; elles ne deviennent pas « inconnues ».
- Une vCard sans
UIDest dédupliquée par contenu à l’import. Beaucoup de vieux exports et de CSV convertis n’ont pas d’identifiant, et chaque fiche en recevait un neuf, si bien que ré-importer le même fichier dupliquait tout le carnet. Pour ces fiches-là seulement, la même adresse principale et le même nom complet nomment la fiche existante dans la réponse au lieu d’en créer une, corbeille comprise ; une fiche qui porte un identifiant n’est jamais dédupliquée par contenu (deux fiches distinctes partagent légitimement une adresse), et une fiche sans adresse s’importe comme avant. Même règle pour un import vers un annuaire partagé. addressBookIdsest optionnel à la création et ne classe la fiche nulle part. Présent, il doit être une table d’identifiants verstrue(la colonne est gardée pour un retour arrière) ; absent, il reste vide. C’est lasourcede la fiche qui dit où elle appartient.- Le filtre
sourcecompare lasourcede la fiche, celle queContactCard/getsert, surContactCard/querycomme sur/queryChanges:source: "organization"retrouve les projections d’annuaire (il répondait 0 en cherchant la valeur parmi les clés d’addressBookIds), l’alias v1addressBookIdgarde le même sens, et une valeur hors depersonal,organization,derived,importedest refuséeunsupportedFilterau lieu de répondre une liste vide. - Un caractère de contrôle est refusé à l’écriture, par son chemin. Un caractère C0 ou DEL dans
name,nicknames,emails,phones,organizationsoutitlesrépondinvalidPropertiesen nommant le chemin JSON et le point de code (emails/0/address: control character U+000D (CR) is not allowed) ; rien n’est rogné, ce qui est stocké est ce qui a été envoyé. La garde vit dans le magasin, si bien queContact/import,Contact/copy, CardDAV (403avecCARDDAV:valid-address-data) et l’outil de migration y passent aussi.noteset les adresses postales sont multilignes par nature et ne sont pas jugées.Email/setapplique la même règle aux adresses, aux noms d’affichage et aux sujets, si bien que les deux surfaces jugent les mêmes octets de la même façon. - Les numéros de téléphone sont rangés sous leur forme internationale. Un numéro que les plans de numérotation reconnaissent est écrit
+41 79 222 44 55par tous les écrivains (Contact/set,Contact/import,Contact/copy, CardDAV, l’outil de migration). Un numéro national est lu dans le pays que l’organisation a déclaré pour elle-même, jamais dans celui de l’instance ; un numéro qui n’est pas valide, qui peut contenir plus d’un numéro, ou qui est disposé comme les numéros d’un autre pays, est gardé tel qu’écrit. UnContact/setqui a changé un numéro renvoiephones. Le filtrephonetrouve un numéro par ses chiffres, quelle que soit la graphie :079 222,+41 79 222et0041 79 222trouvent la même fiche, et la graphie nationale d’un motif écrit avec son indicatif n’est cherchée qu’au début d’un numéro. Les numéros rangés avant sont convertis paroximail admin contact normalize-phones, etdoctor contact_phone_formavertit tant qu’il en reste. - Les propriétés en table contiennent des objets. Une entrée de
emails,phones,organizations,mediaouanniversariesqui n’est pas un objet (une chaîne nue comme["ada@example.com"]) est refusée eninvalidProperties: la RFC 9553 associe chaque identifiant à un objet. Une liste d’objets est acceptée. - L’historique de versions est de premier rang.
ContactCard/getHistoryliste les versions antérieures d’une fiche,restoreVersionen ramène une. Les modifications de n’importe quel protocole (JMAP ou CardDAV) sont versionnées pareil. - L’aller-retour est sans perte. Les composantes de nom et d’adresse se transposent complètement entre vCard (3.0/4.0) et JSContact dans les deux sens ; les propriétés vCard sans modèle JSContact sont préservées plutôt qu’abandonnées. Un
PUTCardDAV qui dupliquerait un UID existant d’une autre source est refusé, jamais dupliqué en silence. - La dépréciation est annoncée honnêtement. L’objet de capacité du compte annonce
maxAddressBooksPerCard: 1etmayCreateAddressBook: false, si bien qu’un client lit les limites au lieu de les découvrir par essais. Les quatre méthodesAddressBooksont des bouchons dépréciés qui répondentforbiddenen nommant la dépréciation ; il n’y a pas de successeur, un client abandonne l’appel. - La corbeille est en deux temps (ADR-035), et le balayage des corbeilles détruit ce qui est à la corbeille depuis plus longtemps que l’horizon du locataire, sa surcharge
contacts.trash_retention_daysou à défaut[contacts] trash_retention_days(30 par défaut, plancher 1 ; une surcharge à0ne purge rien), par la sortie du Drive, séquestres respectés et changementDestroyedannoncé (horizons par locataire). - « Dernier échange » regarde en arrière. Les compteurs d’échange (
interactionCount,lastInteractionAt) n’étaient alimentés que vers l’avant, si bien que des correspondants de longue date affichaient « jamais » ;oximail admin contact backfill-interactionsles sème depuis le courrier existant, au même grain que le souscripteur au fil de l’eau (destinataires des messages envoyés, expéditeur des messages reçus, jamais les brouillons), de façon monotone (ne fait jamais reculer un compteur ni une date, donc rejouable), ne touche que les fiches existantes, simulation par défaut et borné en comptes par--limit.
Les annuaires de tenant
Une organisation peut publier des jeux de contacts partagés (« Clients », « Personnel ») tenus sur le plan d’administration. Le choix de conception qui structure toute la surface : un annuaire n’est pas un conteneur dans lequel les membres classent des fiches. Les membres ne voient aucun conteneur. Ce qu’un membre voit, c’est son propre carnet d’adresses, où les entrées arrivent en fiches lisibles seulement, avecsource: "organization" et directorySource: "tenant:<directoryId>" : le plan même qu’alimente un connecteur LDAP ou SCIM externe. Un client qui affiche déjà les contacts d’origine organisationnelle n’a aucun écran à ajouter.
Qui écrit quoi. Les objets annuaire sont réservés au rôle d’administrateur de tenant. Les entrées sont réservées aux editorIds de leur annuaire, qui sont des identifiants de principal : y nommer un groupe est valide et courant, et l’appartenance est résolue par l’expansion des bénéficiaires au moment du contrôle d’accès, jamais stockée déjà expansée. Ajouter quelqu’un à un groupe lui donne donc le droit d’éditer sans toucher à l’annuaire.
Deux comportements à connaître avant de bâtir dessus :
mayEditest calculé pour vous, et vous ne pouvez pas le calculer vous-même. Il suit le rôle de tenant de l’appelant et l’expansion de groupes côté serveur : deux comptes lisent donc légitimement des valeurs différentes pour le même annuaire, et le dériver depuiseditorIdsest impossible, cette liste étant aveugle à l’appartenance aux groupes par conception. C’est exactement pour cela qu’il est déclaré dans le canon publié au lieu d’être laissé à la déduction depuis le type TypeScript.- La comptabilité du serveur ne vous appartient pas. La propriété
cardporte la fiche JSContact et rien d’autre : les clés de projection commesourceoudirectoryIdposées à l’intérieur sont refusées, pas écrasées en silence.
notFound, invalidProperties plutôt qu’une coercition, accountNotFound, stateMismatch.