http://djson.dj/index.php?title=Special:NewPages&feed=atom&hideredirs=1&limit=50&offset=&namespace=0&username=&tagfilter=
DJSON - New pages [en]
2024-03-29T15:21:28Z
From DJSON
MediaWiki 1.23.3
http://djson.dj/index.php/Utilisation_Json_comme_base_de_donn%C3%A9es
Utilisation Json comme base de données
2018-04-03T14:31:48Z
<p>Sysop: Created page with " (Cet article a été écrit il y a très longtemps, dans une galaxie très très lointaine, par un nakama inconnu) Aujourd’hui on va voir comment utiliser le Json comme b..."</p>
<hr />
<div><br />
(Cet article a été écrit il y a très longtemps, dans une galaxie très très lointaine, par un nakama inconnu) <br />
<br />
Aujourd’hui on va voir comment utiliser le Json comme base de données. L’avantage est de ne plus avoir besoin de MySQL.<br />
<br />
Cette solution peut être intéressante pour de petits sites qui ne demandent pas trop d’administration.<br />
<br />
Cet article va montrer comment utiliser le format Json et quelques points intéressants &#63042; comme par exemple mettre en place une Api qui va stocker dans un fichier Json des utilisateurs.<br />
<br />
Ce qu’il faut garder en tête ici, c’est que chaque fichier Json peut être comparé à une table d’une base de données.<br />
<br />
<br />
__TOC__<br />
<br />
<br />
==== Variables et constructeur ====<br />
<br />
<pre><br />
class UserJson {<br />
private $id;<br />
private $data;<br />
private $table;<br />
private $table_id;<br />
private $user;<br />
private $users;<br />
<br />
public function __construct(){<br />
$this->table = "users.json";<br />
$this->table_id = "users_id.txt";<br />
<br />
// si le fichier n'existe pas, on le crée<br />
if(!file_exists($this->table)){<br />
$this->createTable();<br />
}<br />
}<br />
</pre><br />
* ise en place des variables<br />
* le constructeur va renseigner quelques informations : le nom du fichier Json et txt.<br />
* on termine en vérifiant si les fichiers n’existent pas déjà, si ce n’est pas le cas on fera appel à la méthode qui les créera.<br />
<br />
<br />
====Les méthodes====<br />
<br />
<pre><br />
// création des fichiers Json et txt<br />
// le fichier txt servira uniquement à l'incrémentation<br />
public function createTable(){<br />
$handle = @fopen($this->table, "a+");<br />
$handle_id = @fopen($this->table_id, "a+");<br />
if($handle && $handle_id) {<br />
fclose($handle);<br />
fclose($handle_id);<br />
return true;<br />
}<br />
}<br />
</pre><br />
<pre><br />
// Ajout d'un nouvel utilisateur<br />
public function addUser($data){<br />
<br />
// info sur le nouvel user<br />
$this->data = $data;<br />
<br />
// on récupère le nouvel ID<br />
$this->id = $this->getId();<br />
<br />
if( $this->id !== null ){<br />
// on ajoute l'ID au nouvel user<br />
$this->data = array('id'=>$this->id) + $this->data;<br />
<br />
// on ajoute le nouvel user au fichier<br />
$file = @fopen($this->table, 'a+');<br />
<br />
fputs($file, json_encode($this->data).',');<br />
<br />
fclose($file);<br />
<br />
return true;<br />
}else{<br />
return "Erreur pour l'ajout de l'utilisateur.";<br />
}<br />
}<br />
</pre><br />
* a méthode getId() récupère un nouvel identifiant qui est contenu dans le fichier txt<br />
* ligne 10 : on teste bien que le résultat n’est pas null sinon c’est qu’il y a eu un problème lors de la création de l’identifiant<br />
* chaque utilisateur est sauvegardé sous cette forme : {« id »:31, »pseudo »: »Iron Man », »date »:1387821811},<br />
<br />
<pre><br />
// on récupère un nouvel ID<br />
private function getId(){<br />
<br />
if( $txt = @fopen($this->table_id, "r+") ){<br />
$this->id = fgets( $txt ); // récupération de la valeur<br />
$this->id = intval( $this->id ); // on vérifie qu’il s’agit bien d’un nombre<br />
$this->id++; // on incrémente<br />
fseek( $txt, 0 ); // réinitialisation du curseur<br />
fputs( $txt, $this->id ); // on écrit le nouveau nombre<br />
fclose($txt);<br />
<br />
return $this->id;<br />
}else{<br />
return null;<br />
}<br />
}<br />
</pre><br />
Une simple méthode pour récupérer la valeur qui se trouve dans le fichier, l’incrémenter et ensuite sauvegarder la nouvelle valeur. J’ai repris la méthode du site openclassrooms pour compter le nombre de téléchargement.<br />
<br />
Ici l’élément important : c’est que la méthode est en private, cela signifie qu’on va pouvoir y faire appel uniquement à l’intérieur de la class. Du coup on est certain qu’elle ne peut pas être appelé de l’extérieur, donc un identifiant sera créé uniquement quand on l’appellera dans la class elle-même.<br />
<pre><br />
// on récupère un user spécifique<br />
public function getUser($id){<br />
<br />
// l'id de l'user qu'on souhaite récupérer<br />
$this->id = $id;<br />
<br />
// liste de tous les users<br />
$datas = $this->getAllUser();<br />
<br />
// on teste si l'id correspond, si oui on renvoie le résultat<br />
foreach ($datas as $key => $row) {<br />
if($row->id == $id ) return $row;<br />
}<br />
<br />
// sinon<br />
return 'Aucun utilisateur ne correspond.';<br />
}<br />
</pre><br />
<pre><br />
// liste de tous les users<br />
public function getAllUser(){<br />
<br />
// Si les users sont déjà définis<br />
if (isset($this->users)) return $this->users;<br />
<br />
// on récupère le contenu du fichier<br />
$contents = file_get_contents($this->table);<br />
$this->users = json_decode("[". substr($contents, 0, -1)."]");<br />
<br />
// on renvoi le tableau des users<br />
return $this->users;<br />
}<br />
</pre><br />
* ligne 5 : elle permet de renvoyer directement tous les utilisateurs si la variable est déjà définie. Cela évite de faire plusieurs fois le travail si on appelle cette méthode plusieurs fois dans un même object<br />
* on récupère le contenu encoder en Json, que l’on va décoder pour pouvoir l’utiliser<br />
<br />
Cependant, les données sont sauvegardées de cette manière :<br />
<pre><br />
{"id":31,"pseudo":"Iron","date":1387821811},{"id":32,"pseudo":"Iron","date":1387821811},{"id":33,"pseudo":"Iron","date":1387821811},<br />
</pre><br />
Et pour pouvoir décoder le format Json les données doivent être comme cela :<br />
<pre><br />
[{"id":31,"pseudo":"Iron","date":1387821811},{"id":31,"pseudo":"Iron","date":1387821811},{"id":31,"pseudo":"Iron","date":1387821811}]<br />
</pre><br />
<br />
C’est pour cela que j’ajoute les [ ] et que je supprime la dernière virgule (ligne 9).<br />
<br />
Il est tout à fait possible de faire autrement, enregistrer directement le tableau des utilisateurs, du coup on aura tout de suite la bonne syntaxe. Cependant, pour chaque ajout il faudrait tout récupérer et ré-écrire à chaque fois … perso je préfère écrire uniquement le nouvel utilisateur <br />
<pre><br />
// modification d'un utilisateur<br />
public function updateUser($id, $data){<br />
$this->id = $id;<br />
$this->data = $data;<br />
<br />
// liste de tous les users<br />
$this->users = $this->getAllUser();<br />
<br />
// on ouvre et on vide le fichier<br />
if($handle = @fopen($this->table, "w+")) {<br />
$this->data = "";<br />
foreach ($this->users as $key => $row) {<br />
// si l'id correspond<br />
// on modifie le nouveau pseudo par exemple<br />
if( $row->id == $this->id ){<br />
if($data['pseudo'] != "") $row->pseudo = $data['pseudo'];<br />
}<br />
<br />
$this->data .= json_encode($row).',';<br />
}<br />
// on ajoute tous les utilisateurs dans le fichier<br />
fputs($handle, $this->data);<br />
<br />
fclose($handle);<br />
return true;<br />
}<br />
}<br />
</pre><br />
<pre><br />
// suppression d'un utilisateur<br />
public function removeUser($id){<br />
$this->id = $id;<br />
<br />
// liste tous les users<br />
$this->users = $this->getAllUser();<br />
<br />
if($handle = @fopen($this->table, "w+")) {<br />
$this->data = "";<br />
foreach ($this->users as $key => $row) {<br />
if($row->id == $this->id )<br />
unset($this->users[$key]);<br />
else<br />
$this->data .= json_encode($row).',';<br />
}<br />
<br />
fputs($handle, $this->data);<br />
<br />
fclose($handle);<br />
return true;<br />
}<br />
}<br />
</pre><br />
La suppression se passe plus ou moins comme la modification, on regarde si l’id correspond, si oui on le supprime du tableau des utilisateurs et ensuite on ré-écrit le tout dans le fichier.<br />
<br />
On n’oublie pas la dernière accolade de la class &#63042;<br />
<pre><br />
}<br />
</pre><br />
<br />
<br />
====C’est fini====<br />
<br />
La class permet de créer, de modifier, de supprimer et de lister tous les utilisateurs. Évidemment, elle peut être améliorée &#63042; ici je propose uniquement une base comme exemple &#63042;<br />
<br />
<br />
====Mais il ne manque rien ?====<br />
<br />
Il manque peut-être les appels à la class &#63042; enfin la création de l’object.<br />
<pre><br />
// initialisation<br />
$json = new UserJson();<br />
<br />
// liste de tous les utilisateurs<br />
print_r( $json->getAllUser() );<br />
<br />
// appel d'un utilisateur spécifique<br />
print_r( $json->getUser(31) );<br />
<br />
// ajout d'un nouvel utilisateur<br />
$user = array('pseudo' => 'Batman', 'date'=>time());<br />
var_dump( $json->addUser($user) );<br />
<br />
// modification d'un utilisateur<br />
$update = array('pseudo'=>'Thor');<br />
var_dump( $json->updateUser(44, $update) );<br />
<br />
// suppression d'un utilisateur<br />
print_r( $json->removeUser(42) );<br />
</pre><br />
<br />
<br />
====Idées d’amélioration ?====<br />
<br />
* Pseudo et email unique<br />
* Rajouter des champs : mdp, email, date de modification etc.<br />
* Mettre en place une 2e table pour classer les utilisateurs par catégories<br />
* Mettre en place un système d’identification (login/mdp)<br />
* etc.</div>
Sysop
http://djson.dj/index.php/Introduction_%C3%A0_JSON
Introduction à JSON
2018-04-02T21:02:33Z
<p>Sysop: </p>
<hr />
<div>https://la-cascade.io/json-pour-les-debutants/<br />
<br />
JSON pour les débutants<br />
<br />
* Nous partons de la page [https://la-cascade.io/json-pour-les-debutants/ du 23 mars 2017 - par Louis Lazaris - Traduite par Pierre Choffé].<br />
* que nous la complèterons peu à peu par des annotats.<br />
<br />
----<br />
<br />
<br />
Dans ce guide et tutoriel JSON, je vais essayer de présenter de manière approfondie JSON, son histoire et son utilité. Je terminerai avec une liste d'outils pratiques JSON qui pourront vous servir dans vos projets à venir.<br />
<br />
<br />
== Qu'est-ce que JSON ? ==<br />
<br />
JSON signifie "JavaScript Object Notation" et c'est un format de données. Autrement dit, c'est une façon de stocker des informations, un peu comme une base de données. Bien que créé indépendamment de la spécification ECMAScript, il est maintenant lié à JavaScript qui inclut un objet JSON, et de nombreux développeurs l'incorporent quasiment comme un sous-ensemble du langage.<br />
<br />
Voici un exemple de syntaxe JSON :<br />
<pre><br />
{<br />
"espèce": "Dog",<br />
"race": "Labrador Retriever",<br />
"couleur": "Yellow",<br />
"âge": 6<br />
}<br />
</pre><br />
Comme vous le voyez, JSON est un format de données consistant en paires de nom/valeur (ou clé/valeur) ayant la forme de chaînes de caractères. Les nom et valeur sont séparés par deux points : et chaque paire est séparée de la suivante par une virgule.<br />
<br />
Bien que trouvant sa source dans JavaScript, beaucoup de langages de programmation (si ce n'est tous ?) peuvent générer et lire le format JSON. Il est donc devenu très populaire pour le stockage, la lecture et le partage d'information dans les applications et services web.<br />
<br />
<br />
==Une brève histoire de JSON==<br />
<br />
Douglas Crockford est l'inventeur de JSON et il maintient le site officiel JSON.org où il est présenté et discuté en détail. ( NdT : vous pouvez écouter Douglas Crockford raconter l'histoire de JSON dans cette vidéo YouYube, intéressante aussi pour comprendre certaines caractéristiques du format).<br />
<br />
La première spécification officielle date de 2013, mais JSON remonte à bien plus loin. Le site web a été lancé en 2002, Yahoo et Google ont commencé à utiliser JSON dès 2005 et 2006 respectivement, et JSON a décollé à partir de là. L'article de wikipedia contient beaucoup de détails sur son histoire si vous voulez en savoir plus.<br />
<br />
<br />
==Différences entre JSON et JavaScript Object==<br />
<br />
Comme son nom l'indique, JSON est plus ou moins un object JavaScript, cependant il y a des différences. Tout d'abord, comme expliqué dans la spécification, "JSON est un format texte facilitant l'échange de données structurées entre tous les langages de programmation". Il est donc universel et non pas limité à JavaScript. En fait, il ne fait pas du tout partie de JavaScript, il est simplement dérivé de la façon dont les objets JavaScripts sont écrits.<br />
<br />
En termes de syntaxe, il y a deux différences principales. Tout d'abord, tous les noms (clés) sont représentés sous forme de chaînes de caractères, autrement dit ils doivent être entre guillemets. Ceci par exemple n'est pas du JSON valide :<br />
<pre><br />
// JSON non valide, mais objet JS valide<br />
{<br />
foo: "bar"<br />
}<br />
</pre><br />
La façon correcte d'écrire ce JSON est :<br />
<pre><br />
// JSON valide<br />
{<br />
"foo": "bar"<br />
}<br />
</pre><br />
Remarquez que JSON requiert non seulement que le nom (la clé) soit entre guillemets, mais aussi que les guillemets soient doubles : les guillemets simples sont possibles sur les objets JavaScript, pas dans JSON.<br />
<br />
L'autre différence majeure est dans le type de données que JSON peut stocker. JSON accepte les valeurs suivantes : <br />
*Objet <br />
*Array <br />
*Nombre <br />
*Chaîne de caractères <br />
*true <br />
*false <br />
*null <br />
<br />
C'est assez similaire à ce qu'on trouve dans les objets JS, mais JSON étant représenté sous forme de texte on ne peut pas lui donner des choses du genre fonctions ou des valeurs dynamiques de dates utilisant Date(). Par conséquent, il n'y a pas de méthodes ou autres fonctionnalités dans JSON, il n'y a que du texte. Et c'est bien ainsi, car c'est ce qui en fait un format universel d'échange de données.<br />
<br />
Il est important de noter qu'un morceau complet de JSON est lui-même techniquement un objet JSON, et le type Objet est ce qui permet d'imbriquer des objets JSON comme valeurs, un peu comme les objets dans JavaScript. Ci-dessous, on a un exemple d'objet JSON imbriqué :<br />
<pre><br />
// JSON valide<br />
{<br />
"species": "Dog",<br />
"breed": "Labrador Retriever",<br />
"age": 6,<br />
"traits": {<br />
"eyeColor": "brown",<br />
"coatColor": "yellow",<br />
"weight": "137lbs"<br />
}<br />
}<br />
</pre><br />
Ici, l'objet JSON racine a 4 paires de clés/valeurs (“species”, “breed”, “age”, et “traits”) mais la quatrième valeur est un objet imbriqué comprenant 3 paires de clés/valeurs. Et comme vous l'avez sans doute deviné, on peut imbriquer les objets à l'infini (mais restez raisonnable).<br />
<br />
Un objet JavaScript ressemblerait à ceci :<br />
<pre><br />
// objet JS; non valide en JSON<br />
let myAnimal = {<br />
species: "dog",<br />
breed: "Labrador Retriever",<br />
age: 6,<br />
traits: {<br />
eyeColor: "brown",<br />
coatColor: "yellow",<br />
weight: "137lbs"<br />
}<br />
}<br />
</pre><br />
Vous voyez les différences avec JSON (les guillemets) et de plus, pour que l'objet soit utile en JavaScript il est créé comme valeur d'une variable (myAnimal).<br />
<br />
<br />
== Comment stocker JSON ? ==<br />
<br />
JSON étant du texte, on peut le stocker où l'on veut. Dans une base de données, dans un fichier texte, un stockage client (cookies ou localStorage) ou via son propre format de fichier qui utilise l'extension .json (qui est en gros un fichier texte avec une extension .json).<br />
<br />
Une fois le contenu JSON stocké, il reste donc à pouvoir le récupérer et le parser de manière appropriée. Selon les langages, il y a diverses façons de récupérer et parser JSON, mais puisque nous nous intéressons au développement front-end, je vais montrer comment le faire en JavaScript.<br />
<br />
JavaScript propose deux méthodes, qui font partie de la spécification ECMAScript, pour réaliser deux tâches distinctes.<br />
<br />
<br />
==== Utiliser JSON.stringify() ====<br />
<br />
Admettons que votre application construise des données d'une manière ou d'une autre. Pour conserver ces données quelque part, elles doivent être converties en une chaîne de caractères (string) JSON valide. Vous pouvez le faire avec JSON.stringify() :<br />
<pre><br />
let myJSON = {<br />
species: "Dog",<br />
breed: "Labrador Retriever",<br />
age: 6,<br />
traits: {<br />
eyeColor: "brown",<br />
coatColor: "yellow",<br />
weight: "137lbs"<br />
}<br />
};<br />
<br />
let myNewJSON = JSON.stringify(myJSON, null, '\t');<br />
<br />
/* output of myNewJSON:<br />
{<br />
"species": "Dog",<br />
"breed": "Labrador Retriever",<br />
"age": 6,<br />
"traits": {<br />
"eyeColor": "brown",<br />
"coatColor": "yellow",<br />
"weight": "137lbs"<br />
}<br />
}<br />
*/<br />
</pre><br />
La méthode JSON.stringify() prend un paramètre obligatoire (le JSON que vous voulez convertir en string) et deux arguments optionnels. Le premier est une fonction de remplacement que vous pouvez utiliser pour filtrer certaines paires clé/valeur que vous ne voulez pas inclure. Je n'ai rien exclu dans mon exemple, donc j'ai indiqué null à la place de la fonction de remplacement. D'habitude je n'utilise pas null, mais je voulais utiliser le troisième argument et pour cela il est obligatoire de mentionner le second.<br />
<br />
Le troisième paramètre est la valeur d'espace, il vous aide à formater le JSON de façon à le rendre plus lisible avec indentation, retour à la ligne, etc. Si vous utilisez un nombre pour le troisième argument, il représentera le nombre d'espaces pour l'indentation.<br />
<br />
==== Utiliser JSON.parse() ====<br />
<br />
À l'inverse, si vous recevez du JSON et que vous voulez l'utiliser dans votre JavaScript, vous pouvez utiliser la méthode JSON.parse() :<br />
<pre><br />
let myJSON = '{"species":"Dog","breed":"Labrador Retriever","age":6,"traits":{"eyeColor":"brown","coatColor":"yellow","weight":"137lbs"}}';<br />
<br />
let myNewJSON = JSON.parse(myJSON);<br />
<br />
// logs a JavaScript object, not a string<br />
console.log(myNewJSON);<br />
</pre><br />
Dans l'exemple ci-dessus, je crée manuellement une chaîne de caractères JSON que je stocke dans une variable. C'est juste pour la démonstration, dans un scénario réel le JSON pourrait venir d'une source externe ou d'un fichier JSON séparé.<br />
<br />
La méthode JSON.parse() convertit la chaîne de caractères JSON en un objet que je peux manipuler avec JavaScript. La chaîne de caractères est le seul argument obligatoire de la méthode, mais vous pouvez ajouter un second argument optionnel que l'on appelle un reviver de la fonction. En voici un exemple, qui part du JSON précédent :<br />
<pre><br />
let myNewJSON = JSON.parse(myJSON, function (name, value) {<br />
if (name === "species") {<br />
value = "Cat";<br />
}<br />
return value;<br />
});<br />
</pre><br />
Vous pouvez le voir sur JS Bin<br />
<br />
Si vous regardez le résultat sur JS Bin, vous verrez que notre Labrador Retriever est devenu un chat. C'est juste un exemple qui nous montre que l'on peut modifier les valeurs d'un des noms. Pour plus d'infos sur ces fonctions, vous pouvez consulter MDN ou cet article sur le codage web dynamique.<br />
<br />
<br />
====Utiliser JavaScript pour manipuler du JSON====<br />
<br />
Comme vous l'avez déjà deviné, une fois nos données JSON converties en objet JavaScript, nous pouvons y accéder comme à n'importe quel objet JS. Admettons que nous ayons parsé notre chaîne de caractères JSON et que la variable myNewJSON contienne le résultat, comme nous l'avons vu dans la section précédente. Nous pouvons maintenant utiliser ce qu'on appelle la notation avec point (dot notation) pour accéder aux différentes parties de la donnée JSON :<br />
<pre><br />
console.log(myNewJSON.species); // "Dog"<br />
console.log(myNewJSON.breed); // "Labrador Retriever"<br />
console.log(myNewJSON.age); // 6<br />
</pre><br />
Nous pouvons également accéder à l'objet imbriqué et aux valeurs situées à l'intérieur via la notation avec point :<br />
<pre><br />
console.log(myNewJSON.traits);<br />
/*<br />
[object Object] {<br />
coatColor: "yellow",<br />
eyeColor: "brown",<br />
weight: "137lbs"<br />
}<br />
*/<br />
<br />
console.log(myNewJSON.traits.coatColor); // "yellow"<br />
console.log(myNewJSON.traits.eyeColor); // "brown"<br />
console.log(myNewJSON.traits.weight); // "137lbs"<br />
</pre><br />
Nous pouvons ensuite faire ce que nous voulons de ces données, par exemple ajouter de nouvelles valeurs, changer les valeurs actuelles, effacer des paires de clés/valeurs...<br />
<pre><br />
myNewJSON.age = 7;<br />
delete myNewJSON.traits.weight;<br />
myNewJSON.traits.cuddly = true;<br />
<br />
console.log(myNewJSON);<br />
<br />
/*<br />
[object Object] {<br />
age: 7,<br />
species: "Dog",<br />
breed: "Labrador Retriever",<br />
traits: [object Object] {<br />
coatColor: "yellow",<br />
cuddly: true,<br />
eyeColor: "brown"<br />
}<br />
}<br />
*/<br />
</pre><br />
Dans le code ci-dessus, j'ai modifié la valeur de l'age, supprimé la propriété weight (poids) de l'objet traits, et ajouté une nouvelle propriété cuddly (câlin) à l'objet traits.<br />
<br />
Nous pouvons ensuite utiliser JSON.stringify() pour convertir des nouvelles données dans le format d'origine de façon à pouvoir les stocker où nous voulons comme du JSON valide.<br />
<br />
<br />
== JSON mieux que XML ? ==<br />
<br />
XML n'est certainement pas un format en voie de disparition, mais JSON le surpasse largement en popularité. Douglas Crockford explique les avantages de JSON sur XML ("le XML sans gras") et je cite un extrait ici :<br />
<br />
XML n'est pas idéal pour les échanges de données, de même qu'un tournevis n'est pas fait pour enfoncer des clous. Il porte avec lui un lourd bagage et ne correspond pas au modèle de données de la plupart des langages de programmation. Lorsque les programmeurs ont vu XML pour la première fois, ils ont été choqués par sa laideur et son inefficacité. Cette première réaction était la bonne. Il existe une autre notation textuelle qui posséde tous les avantages de XML et se révèle bien mieux adaptée aux échanges de données cette notation est JSON.<br />
<br />
Il poursuit en détaillant les avantages proclamés de XML et en montrant pourquoi JSON fait mieux.<br />
<br />
<br />
== Qu'est-ce que JSONP ? ==<br />
<br />
JSONP ("JSON with Padding") est une solution visant à surmonter les restrictions et limitations d'origines croisées (cross-origin) liées aux requêtes pour des ressources localisées sur un domaine différent de celui à l'origine de la requête. Bob Ippolito a proposé en 2005 JSONP, peu de temps après la solution similaire de George Jempty appelée JSON++.<br />
<br />
JSON profite du fait que les éléments <script> ne sont pas liés par les limitations d'origines croisées. C'est ainsi que nous sommes pouvons faire un lien vers des scripts situés sur des CDN distants sans problème.<br />
<br />
Dans cet exemple, nous accédons à des données JSONP via un simple JavaScript :<br />
<pre><br />
function doJSONP(result) {<br />
console.log(result.data);<br />
}<br />
<br />
let script = document.createElement('script');<br />
script.src = 'https://api.github.com/users/impressivewebs?callback=doJSONP'<br />
<br />
document.getElementsByTagName('body')[0].appendChild(script);<br />
</pre><br />
Ce code crée un élément <script>, ajoute une URL dans l'attribut src, puis ajoute (append) le script au document. Dans cet exemple, j'accède à des données utilisateur spécifiques sur GitHub (mes propres données, en fait) en utilisant l'API de GitHub. Si vous ouvrez la démo, vous verrez les données affichées dans la console.<br />
<br />
Si vous utilisez jQuery, vous pouvez faire une requête similaire via la méthode $.getJSON() :<br />
<pre><br />
$.getJSON('https://api.github.com/users/impressivewebs', function (result) {<br />
console.log(result);<br />
});<br />
</pre><br />
Remarquez que dans ce cas les données que je veux sont exactement celles qui sont retournées. Avec la version JavaScript, il me faut creuser un peu plus loin avec la propriété data pour obtenir le JSON voulu.<br />
<br />
Dans tous les cas, après avoir obtenu le résultat je peux utiliser la notation avec point pour accéder aux paires clé/valeur. Par exemple je peux obtenir le nombre de mes followers sur GitHub, l'URL de mon avatar, ma quantité de repos publics et bien d'autres infos.<br />
<br />
<br />
==Comment marche JSONP ?==<br />
<br />
Pour comprendre comment fonctionne JSONP, il faut d'abord comprendre qu'un fichier JSON simple ne peut pas être utilisé de cette façon, sauf si le serveur prépare les données de façon à ce que la réponse soit correcte. Comment la technique JSONP résout-elle le problème ?<br />
<br />
Dans l'exemple JavaScript précédent, vous avez sans doute remarqué que l'URL de l'API GitHub avait un paramètre attaché : callback=doJSON. Cette fonction de callback agit comme un emballage (ou un padding) nous permettant l'accès aux données, car normalement lorsque nous injectons des données JSON via un élément <script> il ne se passera rien, les contenus n'étant pas stockés dans une variable accessible dans notre JavaScript.<br />
<br />
Avec JSONP, au lieu de :<br />
<pre><br />
{<br />
"one": "value_1",<br />
"two": "value_2"<br />
}<br />
</pre><br />
...nous recevons ceci :<br />
<pre><br />
callback({<br />
"one": "value_1",<br />
"two": "value_2"<br />
});<br />
</pre><br />
...où callback est le nom de mon callback. Dans mon exemple, ce serait doJSON.<br />
<br />
Donc, si vous souhaitez qu'on puisse accéder à votre JSON à partir d'un serveur distant, vous devez vous assurer qu'il sera envoyé avec le padding (c'est à dire la fonction d'emballage). Vous pouvez en voir un exemple (négatif) dans ce codepen où j'essaie d'accéder à ces données de la Ligue de Baseball mais le navigateur me répond par un message d'erreur "Uncaught SyntaxError: Unexpected token :" parce que la Ligue de Baseball n'a pas fait en sorte que ses données soient accessibles par JSONP.<br />
<br />
Une discussion détaillée de la résolution de ce problème dépasse les limites de cet article, mais vous trouverez une solution simple dans ce fil de Stack Overflow.<br />
<br />
Il faut faire attention aux problèmes de sécurité liés à JSONP, donc faites quelques recherches si vous avez un projet dans ce sens. Pour plus d'infos sur JSONP, vous pouvez consulter les ressources suivantes : <br />
*JSONP sur Wikipedia <br />
*JSONP en termes simples sur Stack Overflow <br />
*Utiliser JSONP en toute sécurité <br />
<br />
== Outils JSON ==<br />
<br />
Il existe de nombreux outils pour faire beaucoup de choses avec les données JSON. Voici une liste de quelques outils intéressants que j'ai rencontrés : <br />
*JSONLint Un validateur de données JSON. C'est un bon outil pour apprendre les bases de la syntaxes et en quoi elle diffère de la syntaxe objet JavaScript. <br />
*json.browse Vous permet de naviguer, améliorer (prettify), manipuler du JSON à partir d'une source externe ou du JSON copié/collé. Une fonctionnalité intéressante est la possibilité de filtrer les données à partir d'un mot-clé. <br />
*JSONedit Un constructeur visuel de JSON qui facilite la construction de structures JSON complexes avec des types de données différents. <br />
*JSON Schema Un vocabulaire vous permettant d'annoter et de valider des documents JSON. <br />
*JSON API Une spécification pour réaliser des API en JSON. <br />
*CSVJSON Convertisseur de CSV et SQl en JSON <br />
*JSON Formatter Outil en ligne pour valider, améliorer, minifier et convertir les données JSON. <br />
*excelJSON Outil en ligne pour convertir du CSV ou TSV en JSON et JSON en CSV ou TSV. <br />
*Myjson Un entrepôt JSON simple pour votre appli web ou mobile. <br />
*jsonbin.org Un nouveau projet de Remy Sharp, "un entrepôt JSON comme service. Protégé par authentification les données sont stockée en JSON et peuvent être liées". <br />
*Kinto Un entrepôt JSON générique avec possibilité de partage et de synchronisation. <br />
*JSON Generator Outil en ligne pour générer des données aléatoires en JSON. <br />
*Hjson Une extension de syntaxe pour JSON, afin de faciliter la lectire et la correction par les humains, avant d'envoyer les données à la machine. <br />
*JSON Selector Generator Copiez/collez du JSON, puis cliquez sur n'importe quelle donnée et cet outil vous dira quel "sélecteur" utiliser en JavaScript pour accéder à cette donnée. <br />
<br />
== Conclusion ==<br />
<br />
Si JSON était un concept relativement neuf pour vous, j'espère que cet article vous aura donné une bonne idée de ce qu'il peut vous apporter. JSON est une technologie solide, facile à utiliser et puissante parce qu'universelle.<br />
<br />
====Ressources complémentaires en français====<br />
<br />
*JSON, le stockage léger et pratique de données multitypes, par alsacréations<br />
*JSON et jQuery, vidéo par Grafikart<br />
<br />
====Ressources complémentaires en anglais====<br />
<br />
*le site officiel JSON.org<br />
*L'histoire de JSON, racontée par son créateur Douglas Crockford (vidéo)<br />
<br />
Article original paru le 1er mars 2017 dans Impressive Webs<br />
Traduit avec l’aimable autorisation de l’auteur.<br />
Copyright Louis Lazaris © 2017. <br />
<br />
Sur l’auteur : Louis Lazaris est un développeur freelance de Toronto, auteur de nombreuses publications. Il écrit régulièrement à propos de CSS sur Impressive Webs et dans d'autres blogs sur le design web. On peut le suivre sur Twitter.</div>
Sysop
http://djson.dj/index.php/Introduction_%C3%A0_YAML
Introduction à YAML
2017-08-22T12:03:02Z
<p>Sysop: /* Usage avancé */</p>
<hr />
<div><br />
Michel Casabianca casa@sweetohm.net<br />
<br />
Cet article est une introduction à YAML, un langage permettant de représenter des données structurées, comme le ferait XML par exemple, mais de manière plus naturelle et moins verbeuse. On y verra une description de la syntaxe de YAML ainsi que des exemples en Java et Python.<br />
<br />
On trouvera une archive ZIP avec cet article au format PDF ainsi que les exemples à l'adresse : http://www.sweetohm.net/arc/introduction-yaml.zip .<br />
<br />
== Qu'est ce que YAML ? ==<br />
<br />
Le nom YAML veut dire "YAML Ain't Markup Language", soit "YAML n'est pas un langage de balises". Si cela met d'emblée des distances avec XML, cela ne nous dit pas ce qu'est YAML. YAML est, d'après sa spécification , un langage de sérialisation de données conçu pour être lisible par des humains et travaillant bien avec les langage de programmation modernes pour les tâches de tous les jours.<br />
<br />
Concrètement, on pourrait noter la liste des ingrédients pour un petit déjeuner de la manière suivante :<br />
<br />
- croissants<br />
- chocolatines <br />
- jambon <br />
- oeufs<br />
<br />
Ceci est un fichier YAML valide qui représente une liste de chaînes de caractères. Pour s'en convaincre, nous pouvons écrire le script Python suivant qui parse le fichier, dont le nom est passé sur la ligne de commande, et affiche le résultat :<br />
<pre><br />
#!/usr/bin/env python<br />
# encoding: UTF-8<br />
<br />
import sys<br />
import yaml<br />
<br />
print yaml.load(open(sys.argv[1]))<br />
</pre><br />
Ce script produira le résultat suivant :<br />
<br />
['croissants', 'chocolatines', 'jambon', 'oeufs']<br />
<br />
Ce qui veut dire que le résultat de ce parsing est une liste Python contenant les chaînes de caractères appropriées ! Le parseur est donc capable de restituer des structures de données ''naturelles'' du langage utilisé pour le parsing.<br />
<br />
Écrivons maintenant un compte à rebours :<br />
<br />
- 3 <br />
- 2 <br />
- 1 <br />
- 0<br />
<br />
Nous obtenons le résultat suivant :<br />
<br />
[3, 2, 1, 0]<br />
<br />
C'est toujours une liste, mais le parseur a reconnu chaque élément comme étant un entier et non une simple chaîne de caractères comme dans l'exemple précédent. Le parseur est donc capable de distinguer des types de données tels que chaînes de caractères et entiers, nombres à virgule flottante et dates. On utilise pour ce faire une syntaxe naturelle : ''3'' est reconnu comme un entier alors que ''croissants'' ne l'est pas car ce dernier ne peut être converti ni en entier, ni en nombre à virgule flottante, ni en tout autre type reconnu par YAML. Pour forcer YAML à interpréter 3 comme une chaîne de caractères, on peut l'entourer de guillemets.<br />
<br />
YAML peut aussi reconnaître des tableaux associatifs, ainsi on pourrait noter une commande de petit déjeuner de la manière suivante :<br />
<br />
croissants: 30<br />
chocolatines: 30 <br />
jambon: 0<br />
oeufs: 0<br />
<br />
Qui sera chargé de la manière suivante :<br />
<br />
{'chocolatines': 30, 'croissants': 30, 'jambon': 0, 'oeufs': 0}<br />
<br />
En combinant les types de données de base dans les collections reconnues par YAML, on peut représenter quasiment toute structure de données. D'autre part, la représentation textuelle de ces données est très lisible et quasiment naturelle.<br />
<br />
Il est aussi possible de réaliser l'opération inverse, à savoir sérialiser des structures de données en mémoire sous forme de texte. Dans l'exemple suivant, nous écrivons sur la sortie standard un ''Dictionnaire'' Python :<br />
<pre><br />
#!/usr/bin/env python # encoding: UTF-8<br />
<br />
import yaml<br />
<br />
recette = {<br />
'nom': 'sushi',<br />
'ingredients': ['riz', 'vinaigre', 'sucre', 'sel', 'thon', 'saumon'], <br />
'temps de cuisson': 10,<br />
'difficulte': 'difficile'<br />
} <br />
print yaml.dump(recette)<br />
</pre><br />
Qui produira la sortie suivante :<br />
<br />
difficulte: difficile<br />
ingredients: [riz, vinaigre, sucre, sel, thon, saumon] <br />
nom: sushi<br />
temps de cuisson: 10<br />
<br />
== Syntaxe de base ==<br />
<br />
Après cette brève introduction, voici une description plus exhaustive de la syntaxe YAML.<br />
<br />
=== Scalaires ===<br />
<br />
Les scalaires sont l'ensemble des types YAML qui ne sont pas des collections (liste ou tableau associatif). Ils peuvent être représentés par une liste de caractères Unicode. Voici une liste des scalaires reconnus par les parseurs YAML :<br />
<br />
==== Chaîne de caractères ====<br />
<br />
Voici un exemple :<br />
<br />
- Chaîne<br />
- "3"<br />
- Chaîne sur<br />
une ligne<br />
- "Guillemets doubles\t"<br />
- 'Guillemets simples\t'<br />
<br />
Qui est parsé de la manière suivante :<br />
<br />
[u'Cha\xeene', '3', u'Cha\xeene sur une ligne', <br />
'Guillemets doubles\t', 'Guillemets simples\\t']<br />
<br />
Le résultat de ce parsing nous amène aux commentaires suivants :<br />
<br />
• Les caractères accentués sont gérés, en fait, l'Unicode est géré de manière plus générale.<br />
• Les retours à la ligne ne sont pas pris en compte dans les chaînes, ils sont gérés comme en HTML ou XML, à savoir qu'ils sont remplacés par des espaces.<br />
• Les guillemets doubles gèrent les caractères d'échappement, comme \t pour la tabulation par exemple.<br />
• Les guillemets simples ne gèrent pas les caractères d'échappement qui sont transcrits de manière littérale.<br />
• La liste des caractères d'échappement gérés par YAML comporte les valeurs classiques, mais aussi nombre d'autres que l'on pourra trouver dans la spécification YAML .<br />
<br />
D'autre part, il est possible d'écrire des caractères Unicode à l'aide des notations suivantes :<br />
<br />
• \xNN : pour écrire des caractères Unicode sur 8 bits, où NN est un nombre hexadécimal. <br />
• \uNNNN : pour des caractères Unicode sur 16 bits.<br />
• \UNNNNNNNN : pour des caractères Unicode sur 32 bits.<br />
<br />
====Entiers ====<br />
<br />
Voici quelques exemples :<br />
<br />
canonique: 12345<br />
decimal: +12_345 <br />
sexagesimal: 3:25:45<br />
octal: 030071<br />
hexadecimal: 0x3039<br />
<br />
Qui est parsé de la manière suivante :<br />
<br />
{'octal': 12345, 'hexadecimal': 12345, 'canonique': 12345,<br />
'decimal': 12345, 'sexagesimal': 12345}<br />
<br />
Nous constatons que les notations les plus courantes des langages de programmation (comme l'octal ou l'hexadécimal) sont gérées. A noter que toutes ces notations seront reconnues comme identiques par un parseur YAML et par conséquent seront équivalentes comme clef d'un tableau associatif par exemple.<br />
<br />
==== Nombres à virgule flottante ====<br />
<br />
Voyons les différentes notations pour ces nombres :<br />
<br />
canonique: 1.23015e+3<br />
exponentielle: 12.3015e+02<br />
sexagesimal: 20:30.15<br />
fixe: 1_230.15<br />
infini negatif: -.inf<br />
pas un nombre: .NaN<br />
<br />
Ce qui est parsé en :<br />
<br />
{'pas un nombre': nan, 'sexagesimal': 1230.1500000000001,<br />
'exponentielle': 1230.1500000000001, 'fixe': 1230.1500000000001,<br />
'infini negatif': -inf, 'canonique': 1230.1500000000001}<br />
<br />
Les notations classiques sont gérées ainsi que les infinis et les valeurs qui ne sont pas des nombres.<br />
<br />
==== Dates ====<br />
<br />
YAML reconnaît aussi des dates :<br />
<br />
canonique: 2001-12-15T02:59:43.1Z<br />
iso8601: 2001-12-14t21:59:43.10-05:00<br />
espace: 2001-12-14 21:59:43.10 -5<br />
date: 2002-12-14<br />
<br />
Qui sont parsées de la manière suivante :<br />
<br />
{'date': datetime.date(2002, 12, 14),<br />
'iso8601': datetime.datetime(2001, 12, 15, 2, 59, 43, 100000), <br />
'canonique': datetime.datetime(2001, 12, 15, 2, 59, 43, 100000),<br />
'espace': datetime.datetime(2001, 12, 15, 2, 59, 43, 100000)}<br />
<br />
Les types résultant du parsing dépendent du langage et du parseur, mais correspondent à des types naturels pour les temps considérés.<br />
<br />
==== Divers ====<br />
<br />
Il existe d'autres scalaires reconnus par les parseurs YAML :<br />
<br />
nul: null<br />
nul bis: ~<br />
vrai: true<br />
vrai bis: yes<br />
vrai ter: on<br />
faux: false<br />
faux bis: no<br />
faux ter: off<br />
<br />
Qui sera parsé en :<br />
<br />
{'faux bis': False, 'vrai ter': True, 'vrai bis': True,<br />
'faux ter': False, 'nul': None, 'faux': False,<br />
'nul bis': None, 'vrai': True}<br />
<br />
Bien sûr, le type des valeurs parsées dépend du langage et d'autres valeurs spéciales peuvent être reconnues suivant les langages et les parseurs. Par exemple, le parseur Ruby reconnaît les symboles (notés :symbole par exemple) et les parse en symboles Ruby.<br />
<br />
== Collections ==<br />
<br />
Il existe deux types de collections reconnues par YAML : les listes et les tableaux associatifs.<br />
<br />
==== Listes ====<br />
<br />
Ce sont des listes ordonnées, et qui peuvent contenir plusieurs éléments identiques (par opposition aux ensembles). Les éléments d'une liste sont identifiés par un tiret, comme suit :<br />
- croissants au<br />
beurre<br />
- chocolatines<br />
- jambon<br />
- oeufs<br />
<br />
Les éléments de la liste sont distingués grâce à l'indentation : le premier élément est identé de manière à ce que sa deuxième ligne soit reconnue comme faisant partie du premier élément de la liste. Cette syntaxe peut être comparée à celle de Python, si ce n'est qu'en YAML, les caractères de tabulation sont strictement interdits pour l'indentation . Cette dernière règle est importante et source de nombreuses erreurs de parsing. Il est important de paramétrer son éditeur de manière à interdire les tabulations pour l'indentation des fichiers YAML.<br />
<br />
Il existe une notation alternative pour les listes, semblable à celle des langages Python ou Ruby :<br />
<br />
[croissants, chocolatines, jambon, oeufs]<br />
<br />
Cette notation, dite en flux , est plus compacte et permet parfois de gagner en lisibilité ou compacité.<br />
<br />
==== Tableaux associatifs ====<br />
<br />
Appelés Map ou Dictionnaires dans certains langages, ils associent une valeur à une clef :<br />
<br />
croissants: 30<br />
chocolatines: 30<br />
jambon: 0<br />
oeufs: 0<br />
<br />
La notation en flux est la suivante :<br />
<br />
{ croissants: 2, chocolatines: 1, jambon: 0, oeufs: 2}<br />
<br />
Qui est parsé de la même manière. Cette notation est identique à celle de Python ou Javascript et se rapproche de celle utilisée par Ruby. A noter qu'il est question que Ruby 2 utilise aussi cette notation.<br />
<br />
==== Commentaires ====<br />
<br />
Il est possible d'inclure des commentaires dans un document de la même manière que dans la plupart des langages de script :<br />
<br />
# commentaire<br />
- Du texte<br />
# autre commentaire<br />
- Autre texte<br />
<br />
A noter que ces commentaires ne doivent (et ne peuvent) contenir d'information utile au parsing dans la mesure où ils ne sont pas accessibles, généralement, au code client du parser.<br />
<br />
==== Documents multiples ====<br />
<br />
Dans un même fichier ou flux, on peut insérer plusieurs documents YAML å la suite, en les faisant commencer par une ligne composée de trois tirets ( --- ) et en les termiant d'une ligne de trois points ( ... ) comme dans l'exemple ci-dessous :<br />
<br />
---<br />
premier document<br />
...<br />
---<br />
deuxième document<br />
...<br />
<br />
A noter que par défaut, les parsers YAML attendent un document par fichier et peuvent émettre une erreur s'ils rencontrent plus d'un document. Il faut alors utiliser une fonction particulière capable de parser des documents multiples (comme yaml.load_all() pour PyYaml par exemple).<br />
<br />
On peut alors extraire ces documents de manière séquentielle du flux.<br />
<br />
== Syntaxe avancée ==<br />
<br />
Avec la section précédente, nous avons vu le minimum vital pour se débrouiller avec YAML. Nous allons maintenant aborder des notions plus avancées dont on peut souvent se passer dans un premier temps.<br />
<br />
==== Références ====<br />
<br />
Les références YAML sont semblables aux pointeurs des langages de programmation. Par exemple :<br />
<br />
lundi: &p 'des patates'<br />
mardi: *p <br />
mercredi: *p <br />
jeudi: *p<br />
vendredi: *p<br />
samedi: *p<br />
dimanche: *p<br />
<br />
Donne, après parsing :<br />
<br />
{'mardi': 'des patates', 'samedi': 'des patates', <br />
'jeudi': 'des patates', 'lundi': 'des patates',<br />
'vendredi': 'des patates', 'dimanche': 'des patates',<br />
'mercredi': 'des patates'}<br />
<br />
A noter qu'un alias, indiqué par une astérisque * , doit pointer vers une ancre valide, indiquée par une esperluette & , sans quoi il en résulte une erreur de parsing. Ainsi le fichier suivant doit provoquer une erreur lors du parsing :<br />
<br />
*foo<br />
<br />
==== Tags ====<br />
<br />
Les tags sont les indicateurs du type de données. Par défaut, il n'est pas nécessaire d'indiquer le type des données qui est déduit de leur forme. Cependant, dans certains cas, il peut être nécessaire de forcer le type d'une donnée et YAML définit les types par défaut suivants :<br />
<pre><br />
null: !!null<br />
integer: !!int 3<br />
float: !!float 1.2<br />
string: !!str string<br />
boolean: !!bool true<br />
binary: !!binary dGVzdA==<br />
map: !!map { key: value }<br />
seq: !!seq [ element1, element2 ]<br />
ensemble: !!set { element1, element2 }<br />
omap: !!omap [ key: value ]<br />
</pre><br />
Les tags correspondants commencent par deux points d'exclamation. Lors du parsing, on obtient les type suivants en Python :<br />
<pre><br />
{'binary': 'test', 'string': 'string',<br />
'seq': ['element1', 'element2'],<br />
'map': {'key': 'value'},<br />
'float': 1.2,<br />
'boolean': True,<br />
'omap': [('key', 'value')],<br />
None: None,<br />
'integer': 3,<br />
'ensemble': set(['element1', 'element2'])}<br />
</pre><br />
A noter les deux types supplémentaires :<br />
<br />
• L'ensemble : il n'est pas ordonné et ne peut comporter de doublon.<br />
• Le tableau associatif ordonné : c'est un tableau associatif dont les entrées sont ordonnées.<br />
<br />
L'utilité des tags pour ces types par défaut est limitée. La vrai puissance des tags réside dans la possibilité de définir ses propres tags pour ses propres types de données.<br />
<br />
Par exemple, on pourrait définir son propre type pour les personnes, comportant deux champs : le nom et le prénom. On doit tout d'abord déclarer le tag au début du document, puis on peut l'utiliser dans la suite, comme dans cet exemple :<br />
<pre><br />
%TAG !personne! tag:foo.org,2004:bar<br />
---<br />
- !personne<br />
nom: Simpson<br />
prenom: Omer<br />
- !personne<br />
nom: Simpson<br />
prenom: Bart<br />
</pre><br />
Nous verrons plus loin comment utiliser les tags avec les APIs Java et Python pour désérialiser des structures YAML en types de donnés personnalisés.<br />
<br />
Il est aussi possible de ne pas déclarer le tag et de l'expliciter dans le document, de la manière suivante :<br />
<br />
- !<tag:foo.org,2004:bar><br />
nom: Simpson<br />
prenom: Omer<br />
- !<tag:foo.org,2004:bar><br />
nom: Simpson<br />
prenom: Bart<br />
<br />
== Directives ==<br />
<br />
Les directives donnent des instructions au parser. Il en existe deux :<br />
<br />
==== TAG ====<br />
<br />
Comme vu précédemment, déclare un tag dans le document.<br />
<br />
==== YAML ====<br />
<br />
Indique la version de YAML du document. Doit être en en-tête du document, comme dans l'exemple ci-dessous :<br />
<br />
%YAML 1.1<br />
---<br />
test<br />
<br />
Un parser doit refuser de traiter un document d'une version majeure supérieure. Par exemple, un parser 1.1 devrait refuser de parser un document en version YAML 2.0 . Il devrait émettre un warning si on lui demande de parser un document de version mineure supérieure, comme 1.2 par exemple. Il doit parser sans protester toutes les versions égales ou inférieures, telles que 1.1 et 1.0 .<br />
<br />
==== Jeu de caractères et encodage ====<br />
<br />
Un parser YAML doit accepter tout caractère Unicode, à l'exception de certains caractères spéciaux . Ces caractères peuvent être encodés en UTF-8 (encodage par défaut), UTF-16 ou UTF-32 . Les parsers YAML sont capables de déterminer l'encodage du texte en examinant le premier caractère. Il est donc impossible d'utiliser tout autre encodage dans un fichier YAML et en particulier <u>ISO-8859-1</u>.<br />
<br />
== APIs YAML ==<br />
<br />
Nous allons maintenant jouer avec les principales APIs YAML.<br />
<br />
==== JYaml ====<br />
<br />
JYaml est une bibliothèque OpenSource pour manipuler les documents YAML en Java. Le projet est hébergé par SourceForge et on trouvera sa page à l'adresse http://jyaml.sourceforge.net/ . On trouvera sur ce site un tutoriel ainsi que les références de l'API .<br />
<br />
==== Utilisation de base ====<br />
<br />
JYaml effectue un mapping par défaut des structures YAML en objets Java standards : il associe une liste à une instance de java.util.ArrayList , un tableau associatif à une instance de java.util.HashMap et les types primitifs de YAML à leur contrepartie Java.<br />
<br />
Ainsi, pour charger un fichier YAML dans un objet Java, on écrira le code suivant :<br />
<br />
Object object = Yaml.load(new File("object.yml"));<br />
<br />
Par exemple, le fichier suivant :<br />
<br />
- Un<br />
- 2<br />
- { trois: 3.0, quatre: true }<br />
<br />
Pourra être chargé en mémoire et affiché dans le terminal avec le source suivant :<br />
<pre><br />
package jyaml;<br />
<br />
import java.io.File;<br />
import org.ho.yaml.Yaml;<br />
<br />
public class Load {<br />
<br />
public static void main(String[] args)<br />
throws Exception {<br />
String filename = "test/object.yml";<br />
if (args.length > 0) filename = args[0];<br />
System.out.println(Yaml.load(new File(filename)));<br />
}<br />
}<br />
</pre><br />
Cela affichera dans le terminal :<br />
<br />
[Un, 2, {quatre=true, trois=3.0}]<br />
<br />
Inversement, on peut sérialiser un objet Java dans un fichier YAML de la manière suivante :<br />
<br />
Yaml.dump(object, new File("dump.yml"));<br />
<br />
Ainsi, on pourra par exemple sérialiser une structure d'objets Java avec le code suivant :<br />
<pre><br />
package jyaml;<br />
<br />
import java.io.File;<br />
import java.util.ArrayList;<br />
import java.util.HashMap;<br />
import java.util.List;<br />
import java.util.Map;<br />
import org.ho.yaml.Yaml;<br />
<br />
public class Dump {<br />
<br />
public static void main(String[] args)<br />
throws Exception {<br />
List<Object> object = new ArrayList<Object>();<br />
object.add("Un");<br />
object.add(2);<br />
Map<String, Object> map = new HashMap<String, Object>();<br />
map.put("trois", 3.0);<br />
map.put("quatre", true);<br />
object.add(map);<br />
Yaml.dump(object, new File("test/dump.yml"));<br />
}<br />
}<br />
</pre><br />
Ce code produira le fichier suivant :<br />
<pre><br />
---<br />
<br />
- Un<br />
- 2<br />
- !java.util.HashMap<br />
quatre: true<br />
trois: !java.lang.Double 3.0<br />
</pre><br />
A noter que ce dump est un peu décevant dans la mesure où certains types standards de YAML (comme les tableaux associatifs et les nombres à virgule flottante) sont sérialisés en types Java (comme java.util.HashMap et java.lang.Double ). Un tel fichier ne sera pas chargé correctement en utilisant un autre langage de programmation (voire même une autre implémentation en Java).<br />
<br />
==== Usage avancé ====<br />
<br />
Nous pouvons aussi travailler avec des types qui ne sont pas génériques et ainsi charger des instances de classes Java à partir de fichiers YAML.<br />
<br />
La première solution consiste à indiquer le type des objets avec des tags YAML. Ainsi, le fichier YAML suivant :<br />
<pre><br />
--- !jyaml.Commande id: test123 articles:<br />
<br />
- !jyaml.Article <br />
id: test456<br />
prix: 3.5<br />
quantite: 1<br />
- !jyaml.Article<br />
id: test567<br />
prix: 2.0<br />
quantite: 2<br />
</pre><br />
Sera-t-il chargé en utilisant les classes suivantes :<br />
<pre><br />
package jyaml;<br />
<br />
public class Commande {<br />
<br />
private String id;<br />
private Article[] articles;<br />
<br />
public String getId() {<br />
return id;<br />
}<br />
<br />
public void setId(String id) {<br />
this.id = id;<br />
}<br />
<br />
public Article[] getArticles() {<br />
return articles;<br />
}<br />
<br />
public void setArticles(Article[] articles) {<br />
this.articles = articles;<br />
}<br />
<br />
public String toString() {<br />
StringBuffer buffer = new StringBuffer("[Commande id='")<br />
.append(id)<br />
.append("', articles='");<br />
for (int i=0; i<articles.length; i++) {<br />
Article article = articles[i];<br />
buffer.append(article.toString());<br />
if (i<articles.length-1) buffer.append(", "); <br />
}<br />
buffer.append("]");<br />
return buffer.toString();<br />
}<br />
}<br />
</pre></div>
Sysop
http://djson.dj/index.php/FAQ
FAQ
2017-05-03T08:58:18Z
<p>Sysop: </p>
<hr />
<div><br/><br />
<br />
;Qu'est-ce que ce blik ? : c'est le mnème (traces mémorielles) en ligne de l'exploration du concept DJSON comme support de formatage d'un intelligramme sous format JSON.<br />
<br />
:{{prim}}Pour contacter le primauteur.<br />
<br />
<br />
;{{ann}}'' Qu'est-ce qu'un annotat ? : c'est une annotation du primauteur au sein d'un texte cité.<br />
<br />
<br/></div>
Sysop
http://djson.dj/index.php/Presentation
Presentation
2017-05-03T08:55:07Z
<p>Sysop: Created page with "<br/> Ce blog a pour but de documenter le processus de réflexion autour de l'exploration du concept DJSON dans le cadre de la communauté globale [http://xlib.re XLIBRE] et d..."</p>
<hr />
<div><br/><br />
Ce blog a pour but de documenter le processus de réflexion autour de l'exploration du concept DJSON dans le cadre de la communauté globale [http://xlib.re XLIBRE] et de l'effort collectif de [http://montpel.net Montpel.Net].<br />
<br />
<br/></div>
Sysop
http://djson.dj/index.php/Blog
Blog
2017-05-03T08:52:32Z
<p>Sysop: Created page with "<br/> {| |width="800"| |width="50"| |width="400"| |- |valign="top"| <big><big>Dernières Contributions</big></big> ---- ==== 20170503 Intelligramme ==== Le propos est un for..."</p>
<hr />
<div><br/><br />
{|<br />
|width="800"|<br />
|width="50"|<br />
|width="400"|<br />
|-<br />
|valign="top"|<br />
<big><big>Dernières Contributions</big></big><br />
----<br />
<br />
==== 20170503 Intelligramme ====<br />
<br />
Le propos est un format de données universel, celui de l'intelligramme délinéé et signodaté. <br />
<br />
1. un intelligramme est un engramme (élément mémoriel) <br />
<br />
* présentable à distance (sous forme de datagramme IP)<br />
* actif, c'est à dire d'un contenu capable d'être ou de se faire modifier.<br />
* intelligible, c'est à dire identifiable comme un élément homogène interliable.<br />
<br />
2. délinéé signifie que des zones sémantiques ou binaires (blobs) peuvent y être définies, identfiées, qualifiées, interliées et/ou exécutées.<br />
<br />
3. signodaté signifie que le contenu est identifié par une date et un auteur.<br />
<br />
L'idée est de partir de JSON (et donc des RFC qui en assurent la compatibilité IP) et de le délinéer (DJSON) de plusieurs manières possibles, ce qui a priori signifie de prédélinéer une zone de description de la présentation, une zone de signodatage et une zone charge utile.<br />
<br />
<br />
----<br />
<br />
<!--/texte--><br />
|<br />
|valign="top"|<br />
<!--date--><br />
<big><big>Références</big></big><br />
----<br />
<br/><br />
* [[Presentation|Présentation courante]]<br />
<br />
<br />
<big><big>Archives</big></big><br />
----<br />
<br/><br />
<!--/date--><br />
<br />
__TOC__<br />
|}</div>
Sysop
http://djson.dj/index.php/Draft_Jenkins_JMAPmail
Draft Jenkins JMAPmail
2017-02-26T12:05:06Z
<p>Sysop: Created page with "<pre> JMAP N. Jenkins Internet-Draft FastMail Intended status: Standa..."</p>
<hr />
<div><pre><br />
JMAP N. Jenkins<br />
Internet-Draft FastMail<br />
Intended status: Standards Track October 19, 2016<br />
Expires: April 22, 2017<br />
<br />
JMAP for Mail<br />
draft-jenkins-jmapmail-00<br />
<br />
Abstract<br />
<br />
This document specifies a data model for synchronising email data<br />
with a server using JMAP.<br />
<br />
Status of This Memo<br />
<br />
This Internet-Draft is submitted in full conformance with the<br />
provisions of BCP 78 and BCP 79.<br />
<br />
Internet-Drafts are working documents of the Internet Engineering<br />
Task Force (IETF). Note that other groups may also distribute<br />
working documents as Internet-Drafts. The list of current Internet-<br />
Drafts is at http://datatracker.ietf.org/drafts/current/.<br />
<br />
Internet-Drafts are draft documents valid for a maximum of six months<br />
and may be updated, replaced, or obsoleted by other documents at any<br />
time. It is inappropriate to use Internet-Drafts as reference<br />
material or to cite them other than as "work in progress."<br />
<br />
This Internet-Draft will expire on April 22, 2017.<br />
<br />
Copyright Notice<br />
<br />
Copyright (c) 2016 IETF Trust and the persons identified as the<br />
document authors. All rights reserved.<br />
<br />
This document is subject to BCP 78 and the IETF Trust's Legal<br />
Provisions Relating to IETF Documents<br />
(http://trustee.ietf.org/license-info) in effect on the date of<br />
publication of this document. Please review these documents<br />
carefully, as they describe your rights and restrictions with respect<br />
to this document. Code Components extracted from this document must<br />
include Simplified BSD License text as described in Section 4.e of<br />
the Trust Legal Provisions and are provided without warranty as<br />
described in the Simplified BSD License.<br />
<br />
Jenkins Expires April 22, 2017 [Page 1]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
Table of Contents<br />
<br />
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
1.1. Notational Conventions . . . . . . . . . . . . . . . . . 3<br />
1.2. Terminology . . . . . . . . . . . . . . . . . . . . . . . 4<br />
1.3. Addition to the capabilities object . . . . . . . . . . . 4<br />
2. Mailboxes . . . . . . . . . . . . . . . . . . . . . . . . . . 4<br />
2.1. getMailboxes . . . . . . . . . . . . . . . . . . . . . . 7<br />
2.2. getMailboxUpdates . . . . . . . . . . . . . . . . . . . . 8<br />
2.3. setMailboxes . . . . . . . . . . . . . . . . . . . . . . 10<br />
2.3.1. Ordering of changes . . . . . . . . . . . . . . . . . 11<br />
2.3.2. Creating mailboxes . . . . . . . . . . . . . . . . . 11<br />
2.3.3. Updating mailboxes . . . . . . . . . . . . . . . . . 12<br />
2.3.4. Destroying mailboxes . . . . . . . . . . . . . . . . 13<br />
2.3.5. Response . . . . . . . . . . . . . . . . . . . . . . 14<br />
3. MessageLists . . . . . . . . . . . . . . . . . . . . . . . . 15<br />
3.1. getMessageList . . . . . . . . . . . . . . . . . . . . . 16<br />
3.1.1. Filtering . . . . . . . . . . . . . . . . . . . . . . 17<br />
3.1.2. Sorting . . . . . . . . . . . . . . . . . . . . . . . 20<br />
3.1.3. Thread collapsing . . . . . . . . . . . . . . . . . . 21<br />
3.1.4. Windowing . . . . . . . . . . . . . . . . . . . . . . 22<br />
3.1.5. Response . . . . . . . . . . . . . . . . . . . . . . 22<br />
3.2. getMessageListUpdates . . . . . . . . . . . . . . . . . . 24<br />
4. Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . 27<br />
4.1. getThreads . . . . . . . . . . . . . . . . . . . . . . . 27<br />
4.2. getThreadUpdates . . . . . . . . . . . . . . . . . . . . 29<br />
5. Messages . . . . . . . . . . . . . . . . . . . . . . . . . . 31<br />
5.1. getMessages . . . . . . . . . . . . . . . . . . . . . . . 35<br />
5.2. getMessageUpdates . . . . . . . . . . . . . . . . . . . . 37<br />
5.3. setMessages . . . . . . . . . . . . . . . . . . . . . . . 39<br />
5.3.1. Saving a draft . . . . . . . . . . . . . . . . . . . 40<br />
5.3.2. Updating messages . . . . . . . . . . . . . . . . . . 43<br />
5.3.3. Sending messages . . . . . . . . . . . . . . . . . . 44<br />
5.3.4. Cancelling a send . . . . . . . . . . . . . . . . . . 44<br />
5.3.5. Destroying messages . . . . . . . . . . . . . . . . . 44<br />
5.3.6. Response . . . . . . . . . . . . . . . . . . . . . . 45<br />
5.4. importMessages . . . . . . . . . . . . . . . . . . . . . 46<br />
5.5. copyMessages . . . . . . . . . . . . . . . . . . . . . . 47<br />
5.6. reportMessages . . . . . . . . . . . . . . . . . . . . . 49<br />
6. Identities . . . . . . . . . . . . . . . . . . . . . . . . . 50<br />
6.1. getIdentities . . . . . . . . . . . . . . . . . . . . . . 51<br />
6.2. getIdentityUpdates . . . . . . . . . . . . . . . . . . . 52<br />
6.3. setIdentities . . . . . . . . . . . . . . . . . . . . . . 54<br />
7. SearchSnippets . . . . . . . . . . . . . . . . . . . . . . . 57<br />
7.1. getSearchSnippets . . . . . . . . . . . . . . . . . . . . 57<br />
8. Vacation Response . . . . . . . . . . . . . . . . . . . . . . 58<br />
8.1. getVacationResponse . . . . . . . . . . . . . . . . . . . 59<br />
8.2. setVacationResponse . . . . . . . . . . . . . . . . . . . 60<br />
<br />
Jenkins Expires April 22, 2017 [Page 2]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
9. References . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
9.1. Normative References . . . . . . . . . . . . . . . . . . 61<br />
9.2. URIs . . . . . . . . . . . . . . . . . . . . . . . . . . 61<br />
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 62<br />
<br />
1. Introduction<br />
<br />
JMAP is a generic protocol for synchronising data, such as mail,<br />
calendars or contacts, between a client and a server. It is<br />
optimised for mobile and web environments, and aims to provide a<br />
consistent interface to different data types.<br />
<br />
This specification defines a data model for synchronising mail<br />
between a client and a server using JMAP.<br />
<br />
1.1. Notational Conventions<br />
<br />
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",<br />
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this<br />
document are to be interpreted as described in [RFC2119].<br />
<br />
The underlying format used for this specification is JSON.<br />
Consequently, the terms "object" and "array" as well as the four<br />
primitive types (strings, numbers, booleans, and null) are to be<br />
interpreted as described in Section 1 of [RFC7159].<br />
<br />
Some examples in this document contain "partial" JSON documents used<br />
for illustrative purposes. In these examples, three periods "..."<br />
are used to indicate a portion of the document that has been removed<br />
for compactness.<br />
<br />
Types signatures are given for all JSON objects in this document.<br />
The following conventions are used:<br />
<br />
o "Boolean|String" - The value is either a JSON "Boolean" value, or<br />
a JSON "String" value.<br />
<br />
o "Foo" - Any name that is not a native JSON type means an object<br />
for which the properties (and their types) are defined elsewhere<br />
within this document.<br />
<br />
o "Foo[]" - An array of objects of type "Foo".<br />
<br />
o "String[Foo]" - A JSON "Object" being used as a map (associative<br />
array), where all the values are of type "Foo".<br />
<br />
Jenkins Expires April 22, 2017 [Page 3]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
1.2. Terminology<br />
<br />
The same terminology is used in this document as in the core JMAP<br />
specification.<br />
<br />
1.3. Addition to the capabilities object<br />
<br />
The capabilities object is returned as part of the standard JMAP<br />
authentication response; see the JMAP spec. Servers supporting<br />
_this_ specification MUST add a property called "{TODO: URI for this<br />
spec}" to the capabilities object. The value of this property is an<br />
object which SHOULD contain the following information on server<br />
capabilities:<br />
<br />
o *maxSizeMessageAttachments*: "Number" The maximum total size of<br />
attachments, in bytes, allowed for messages. A server MAY still<br />
reject messages with a lower attachment size total (for example,<br />
if the body includes several megabytes of text, causing the size<br />
of the encoded MIME structure to be over some server-defined<br />
limit).<br />
<br />
o *canDelaySend*: "Boolean" Does the server support inserting a<br />
message into the outbox to be sent later at a user-specified time?<br />
<br />
o *messageListSortOptions*: "String[]" A list of all the message<br />
properties the server supports for sorting by. This MAY include<br />
properties the client does not recognise (for example custom<br />
properties specified in a vendor extension). Clients MUST ignore<br />
any unknown properties in the list.<br />
<br />
2. Mailboxes<br />
<br />
A mailbox represents a named set of emails. This is the primary<br />
mechanism for organising messages within an account. It is analogous<br />
to a folder in IMAP or a label in other systems. A mailbox may<br />
perform a certain role in the system; see below for more details.<br />
<br />
For compatibility with IMAP, a message MUST belong to one or more<br />
mailboxes. The message id does not change if the message changes<br />
mailboxes.<br />
<br />
A *Mailbox* object has the following properties:<br />
<br />
o *id*: "String" The id of the mailbox. This property is immutable.<br />
<br />
o *name*: "String" User-visible name for the mailbox, e.g. "Inbox".<br />
This may be any UTF-8 string of at least 1 character in length and<br />
<br />
Jenkins Expires April 22, 2017 [Page 4]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
maximum 256 bytes in size. Servers SHOULD forbid sibling<br />
Mailboxes with the same name.<br />
<br />
o *parentId*: "String|null" The mailbox id for the parent of this<br />
mailbox, or "null" if this mailbox is at the top level. Mailboxes<br />
form acyclic graphs (forests) directed by the child-to-parent<br />
relationship. There MUST NOT be a loop.<br />
<br />
o *role*: "String|null" Identifies system mailboxes. This property<br />
can only be set on create. After the record has been created,<br />
this property is immutable. The following values MUST be used for<br />
the relevant mailboxes:<br />
<br />
* "inbox" - the mailbox to which new mail is delivered by<br />
default, unless diverted by a rule or spam filter etc.<br />
<br />
* "archive" - messages the user does not need right now, but does<br />
not wish to delete.<br />
<br />
* "drafts" - messages the user is currently writing and are not<br />
yet sent.<br />
<br />
* "outbox" - messages the user has finished writing and wishes to<br />
send (see the "setMessages" method description for more<br />
information). A mailbox with this role MUST be present if the<br />
user is allowed to send mail through an account. If not<br />
present, the user MAY NOT send mail with that account.<br />
<br />
* "sent" - messages the user has sent.<br />
<br />
* "trash" - messages the user has deleted.<br />
<br />
* "spam" - messages considered spam by the server.<br />
<br />
* "templates" - drafts which should be used as templates (i.e.<br />
used as the basis for creating new drafts).<br />
<br />
No two mailboxes may have the same role. Mailboxes without a<br />
known purpose MUST have a role of "null". An account is not<br />
required to have mailboxes with any of the above roles. A client<br />
MAY create new mailboxes with a role property to help them keep<br />
track of a use-case not covered by the above list. To avoid<br />
potential conflict with any special behaviour a server might apply<br />
to mailboxes with certain roles in the future, any roles not in<br />
the above list created by the client must begin with ""x-"". The<br />
client MAY attempt to create mailboxes with the standard roles if<br />
not already present, but the server MAY reject these.<br />
<br />
Jenkins Expires April 22, 2017 [Page 5]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *sortOrder*: "Number" Defines the sort order of mailboxes when<br />
presented in the client's UI, so it is consistent between devices.<br />
The number MUST be an integer in the range 0 <= sortOrder < 2^31.<br />
A mailbox with a lower order should be displayed before a mailbox<br />
with a higher order (that has the same parent) in any mailbox<br />
listing in the client's UI. Mailboxes with equal order SHOULD be<br />
sorted in alphabetical order by name. The sorting SHOULD take<br />
into account locale-specific character order convention.<br />
<br />
o *mustBeOnlyMailbox*: "Boolean" If "true", messages in this mailbox<br />
may not also be in any other mailbox that also has<br />
"mustBeOnlyMailbox: true". If "false", messages may be added to<br />
this mailbox in addition to any other mailbox (i.e. it's a label).<br />
This property is immutable.<br />
<br />
o *mayReadItems*: "Boolean" If true, may use this mailbox as part of<br />
a filter in a _getMessageList_ call. If a submailbox is shared<br />
but not the parent mailbox, this may be "false".<br />
<br />
o *mayAddItems*: "Boolean" The user may add messages to this mailbox<br />
(by either creating a new message or moving an existing one).<br />
<br />
o *mayRemoveItems*: "Boolean" The user may remove messages from this<br />
mailbox (by either changing the mailboxes of a message or deleting<br />
it).<br />
<br />
o *mayCreateChild*: "Boolean" The user may create a mailbox with<br />
this mailbox as its parent.<br />
<br />
o *mayRename*: "Boolean" The user may rename the mailbox or make it<br />
a child of another mailbox.<br />
<br />
o *mayDelete*: "Boolean" The user may delete the mailbox itself.<br />
<br />
o *totalMessages*: "Number" The number of messages in this mailbox.<br />
<br />
o *unreadMessages*: "Number" The number of messages in this mailbox<br />
where the _isUnread_ property of the message is "true" and the<br />
_isDraft_ property is "false".<br />
<br />
o *totalThreads*: "Number" The number of threads where at least one<br />
message in the thread is in this mailbox (but see below for<br />
special case handling of Trash).<br />
<br />
o *unreadThreads*: "Number" The number of threads where at least one<br />
message in the thread has "isUnread == true" and "isDraft ==<br />
false" AND at least one message in the thread is in this mailbox<br />
<br />
Jenkins Expires April 22, 2017 [Page 6]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
(but see below for special case handling of Trash). Note, the<br />
unread message does not need to be the one in this mailbox.<br />
<br />
The Trash mailbox (that is a mailbox with "role == "trash"") MUST be<br />
treated specially:<br />
<br />
o Messages in the Trash are ignored when calculating the<br />
"unreadThreads" and "totalThreads" count of other mailboxes.<br />
<br />
o Messages not in the Trash are ignored when calculating the<br />
"unreadThreads" and "totalThreads" count for the Trash mailbox.<br />
<br />
The result of this is that messages in the Trash are treated as<br />
though they are in a separate thread for the purposes of mailbox<br />
counts. It is expected that clients will hide messages in the Trash<br />
when viewing a thread in another mailbox and vice versa. This allows<br />
you to delete a single message to the Trash out of a thread.<br />
<br />
For example, suppose you have an account where the entire contents is<br />
a single conversation with 2 messages: an unread message in the Trash<br />
and a read message in the Inbox. The "unreadThreads" count would be<br />
"1" for the Trash and "0" for the Inbox.<br />
<br />
Destroying a mailbox MUST NOT delete any messages still contained<br />
within it. It only removes them from the mailbox. Since messages<br />
MUST always be in at least one mailbox, if the last mailbox they are<br />
in is deleted the messages must be added to the mailbox with "role ==<br />
"inbox"". If no Inbox exists, the messages must be moved to any<br />
other mailbox; this is server dependent.<br />
<br />
2.1. getMailboxes<br />
<br />
Mailboxes can either be fetched explicitly by id, or all of them at<br />
once. To fetch mailboxes, make a call to "getMailboxes". It takes<br />
the following arguments:<br />
<br />
o *accountId*: "String|null" The Account to fetch the mailboxes for.<br />
If "null", the primary account is used.<br />
<br />
o *ids*: "String[]|null" The ids of the mailboxes to fetch. If<br />
"null", all mailboxes in the account are returned.<br />
<br />
o *properties*: "String[]|null" The properties of each mailbox to<br />
fetch. If "null", all properties are returned. The id of the<br />
mailbox will *always* be returned, even if not explicitly<br />
requested.<br />
<br />
Jenkins Expires April 22, 2017 [Page 7]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
The response to _getMailboxes_ is called _mailboxes_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *state*: "String" A string representing the state on the server<br />
for *all* mailboxes. If the state of a mailbox changes, or a new<br />
mailbox is created, or a mailbox is destroyed, this string will<br />
change. It is used to get delta updates.<br />
<br />
o *list*: "Mailbox[]" An array of the Mailbox objects requested.<br />
This will be the *empty array* if the _ids_ argument was the empty<br />
array, or contained only ids for mailboxes that could not be<br />
found.<br />
<br />
o *notFound*: "String[]|null" This array contains the ids passed to<br />
the method for mailboxes that do not exist, or "null" if all<br />
requested ids were found. It MUST be "null" if the _ids_ argument<br />
in the call was "null".<br />
<br />
The following errors may be returned instead of the _mailboxes_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"requestTooLarge": Returned if the number of _ids_ requested by the<br />
client exceeds the maximum number the server is willing to process in<br />
a single method call.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A "description" property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
2.2. getMailboxUpdates<br />
<br />
The _getMailboxUpdates_ call allows a client to efficiently update<br />
the state of its cached mailboxes to match the new state on the<br />
server. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", the primary account will be used.<br />
<br />
Jenkins Expires April 22, 2017 [Page 8]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the<br />
_mailboxes_ response. The server will return the changes made<br />
since this state.<br />
<br />
o *fetchRecords*: "Boolean|null" If "true", immediately after<br />
outputting a _mailboxUpdates_ response, an implicit call will be<br />
made to _getMailboxes_ with the _changed_ property of the response<br />
as the _ids_ argument, and the _fetchRecordProperties_ argument as<br />
the _properties_ argument. If "false" or "null", no implicit call<br />
will be made.<br />
<br />
o *fetchRecordProperties*: "String[]|null" If "null", all Mailbox<br />
properties will be fetched unless _onlyCountsChanged_ in the<br />
_mailboxUpdates_ response is "true", in which case only the 4<br />
counts properties will be returned (_totalMessages_,<br />
_unreadMessages_, _totalThreads_ and _unreadThreads_). If not<br />
"null", this value will be passed through to the _getMailboxes_<br />
call regardless of the _onlyCountsChanged_ value in the<br />
_mailboxUpdates_ response.<br />
<br />
The response to _getMailboxUpdates_ is called _mailboxUpdates_. It<br />
has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String" This is the _sinceState_ argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *changed*: "String[]" An array of Mailbox ids where a property of<br />
the mailbox has changed between the old state and the new state,<br />
or the mailbox has been created, and the mailbox has not been<br />
destroyed.<br />
<br />
o *removed*: "String[]" An array of Mailbox ids for mailboxes which<br />
have been destroyed since the old state.<br />
<br />
o *onlyCountsChanged*: "Boolean" Indicates that only the mailbox<br />
counts (unread/total messages/threads) have changed since the old<br />
state. The client can then use this to optimise its data transfer<br />
and only fetch the counts. If the server is unable to tell if<br />
only counts have changed, it should just always return "false".<br />
<br />
If a mailbox has been modified AND deleted since the oldState, the<br />
server should just return the id in the _removed_ array, but MAY<br />
<br />
Jenkins Expires April 22, 2017 [Page 9]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
return it in the _changed_ array as well. If a mailbox has been<br />
created AND deleted since the oldState, the server SHOULD remove the<br />
mailbox id from the response entirely, but MAY include it in the<br />
_removed_ array.<br />
<br />
The following errors may be returned instead of the "mailboxUpdates"<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A "description" property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old. The client MUST invalidate its<br />
Mailbox cache.<br />
<br />
2.3. setMailboxes<br />
<br />
Mailboxes can be created, updated and destroyed using the<br />
_setMailboxes_ method. The method takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", defaults to the primary account.<br />
<br />
o *ifInState*: "String|null" This is a state string as returned by<br />
the _getMailboxes_ method. If supplied, the string must match the<br />
current state, otherwise the method will be aborted and a<br />
"stateMismatch" error returned. If "null", any changes will be<br />
applied to the current state.<br />
<br />
o *create*: "String[Mailbox]|null" A map of _creation id_ (an<br />
arbitrary string set by the client) to Mailbox objects. If<br />
"null", no objects will be created.<br />
<br />
o *update*: "String[Mailbox]|null" A map of mailbox id to objects<br />
containing the properties to update for that Mailbox. If "null",<br />
no objects will be updated.<br />
<br />
Jenkins Expires April 22, 2017 [Page 10]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *destroy*: "String[]|null" A list of ids for Mailboxes to<br />
permanently delete. If "null", no objects will be deleted.<br />
<br />
If a create, update or destroy is rejected, the appropriate error<br />
MUST be added to the notCreated/notUpdated/notDestroyed property of<br />
the response and the server MUST continue to the next create/update/<br />
destroy. It does not terminate the method.<br />
<br />
2.3.1. Ordering of changes<br />
<br />
Each create, update or destroy is considered an atomic unit. The<br />
server MAY commit some of the changes but not others, however MUST<br />
NOT only commit part of an update to a single record (e.g. update the<br />
_name_ field but not the _parentId_ field, if both are supplied in<br />
the update object).<br />
<br />
The final state MUST be valid after the setMailboxes is finished,<br />
however the server MAY have to transition through invalid<br />
intermediate states (not exposed to the client) while processing the<br />
individual create/update/destroy requests. For example, a single<br />
method call could rename Mailbox A => B, and simultaneously rename<br />
Mailbox B => A. The final state is valid, so this is allowed,<br />
however if processed sequentially there will be an internal state<br />
where temporarily both mailboxes have the same name.<br />
<br />
A Mailbox may reference another Mailbox object as a parent. When a<br />
Mailbox is created or modified, it may reference another Mailbox<br />
being created _in the same API request_ by using the creation id<br />
prefixed with a "#". The order of the method calls in the request by<br />
the client MUST be such that the mailbox being referenced is created<br />
in *either the same or an earlier method call*. If within the same<br />
method call, the server MUST process the parent create first, as if<br />
this fails the create/update that references it will also fail.<br />
<br />
Creation ids sent by the client SHOULD be unique within the single<br />
API request for a particular data type. If a creation id is reused,<br />
the server MUST map the creation id to the most recently created item<br />
with that id.<br />
<br />
2.3.2. Creating mailboxes<br />
<br />
The properties of the Mailbox object submitted for creation MUST<br />
conform to the following conditions:<br />
<br />
o The _id_ property MUST NOT be present.<br />
<br />
o The _parentId_ property MUST be either "null" or be a valid id for<br />
a mailbox for which the "mayCreateChild" property is "true".<br />
<br />
Jenkins Expires April 22, 2017 [Page 11]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o The _role_ property MUST be either "null", a valid role as listed<br />
in the Mailbox object specification, or prefixed by ""x-"".<br />
<br />
o The _mustBeOnlyMailbox_ property MUST NOT be present. This is<br />
server dependent and will be set by the server.<br />
<br />
o The _mayXXX_ properties MUST NOT be present. Restrictions may<br />
only be set by the server for system mailboxes, or when sharing<br />
mailboxes with other users (setting sharing is not defined yet in<br />
this spec).<br />
<br />
o The _totalMessages_, _unreadMessages_, _totalThreads_ and<br />
_unreadThreads_ properties MUST NOT be present.<br />
<br />
If any of the properties are invalid, the server MUST reject the<br />
create with an "invalidProperties" error. The Error object SHOULD<br />
contain a property called _properties_ of type "String[]" that lists<br />
*all* the properties that were invalid. The object MAY also contain<br />
a _description_ property of type "String" with a user-friendly<br />
description of the problems.<br />
<br />
There may be a maximum number of mailboxes allowed on the server. If<br />
this is reached, any attempt at creation will be rejected with a<br />
"maxQuotaReached" error.<br />
<br />
2.3.3. Updating mailboxes<br />
<br />
If the _id_ given does not correspond to a Mailbox in the given<br />
account, the update MUST be rejected with a "notFound" error.<br />
<br />
All properties being updated must be of the correct type, not<br />
immutable or server-set-only, and the new value must obey all<br />
conditions of the property. In particular, note the following<br />
conditions:<br />
<br />
o The _name_ property MUST be a valid UTF-8 string of at least 1<br />
character in length and maximum 256 bytes in size.<br />
<br />
o The _parentId_ property MUST be either "null" or be a valid id for<br />
_another_ mailbox that is *not a descendant* of this mailbox, and<br />
for which the "mayCreateChild" property is "true".<br />
<br />
o These properties are immutable or may only be set by the server:<br />
<br />
* id<br />
<br />
* role<br />
<br />
Jenkins Expires April 22, 2017 [Page 12]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
* mustBeOnlyMailbox<br />
<br />
* mayReadItems<br />
<br />
* mayAddItems<br />
<br />
* mayRemoveItems<br />
<br />
* mayCreateChild<br />
<br />
* mayRename<br />
<br />
* mayDelete<br />
<br />
* totalMessages<br />
<br />
* unreadMessages<br />
<br />
* totalThreads<br />
<br />
* unreadThreads<br />
<br />
If any of the properties are invalid, the server MUST reject the<br />
update with an "invalidProperties" error. The Error object SHOULD<br />
contain a property called _properties_ of type "String[]" that lists<br />
*all* the properties that were invalid. The object MAY also contain<br />
a _description_ property of type "String" with a user-friendly<br />
description of the problems.<br />
<br />
2.3.4. Destroying mailboxes<br />
<br />
If the _id_ given does not correspond to a Mailbox in the given<br />
account, the destruction MUST be rejected with a "notFound" error.<br />
<br />
If the mailbox has "mayDeleteMailbox == false", the destruction MUST<br />
be rejected with a "forbidden" error.<br />
<br />
A mailbox MAY NOT be destroyed if it still has any child mailboxes.<br />
Attempting to do so will result in the destruction being rejected<br />
with a "mailboxHasChild" error.<br />
<br />
Destroying a mailbox MUST NOT delete any messages still contained<br />
within it. It only removes them from the mailbox. Since messages<br />
MUST always be in at least one mailbox, if the last mailbox they are<br />
in is deleted the messages must be added to the mailbox with "role ==<br />
"inbox"". If no Inbox exists, the messages must be moved to any<br />
other mailbox; this is server dependent.<br />
<br />
Jenkins Expires April 22, 2017 [Page 13]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
There MUST always be *at least one* mailbox. It is expected that the<br />
server will enforce this by setting "mayDeleteMailbox == false" on at<br />
least the Inbox, if not all system mailboxes. However, if this is<br />
not the case, an attempt to destroy the last mailbox MUST still be<br />
rejected with a "mailboxRequired" error.<br />
<br />
2.3.5. Response<br />
<br />
The response to _setMailboxes_ is called _mailboxesSet_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String|null" The state string that would have been<br />
returned by "getMailboxes" before making the requested changes, or<br />
"null" if the server doesn't know what the previous state string<br />
was.<br />
<br />
o *newState*: "String" The state string that will now be returned by<br />
"getMailboxes".<br />
<br />
o *created*: "String[Mailbox]" A map of the creation id to an object<br />
containing all server-set properties for each successfully created<br />
Mailbox: _id_, _mustBeOnlyMailbox_, all _mayXXX_ properties,<br />
_totalMessages_, _unreadMessages_, _totalThreads_ and<br />
_unreadThreads_.<br />
<br />
o *updated*: "String[]" A list of ids for Mailboxes that were<br />
successfully updated.<br />
<br />
o *destroyed*: "String[]" A list of ids for Mailboxes that were<br />
successfully destroyed.<br />
<br />
o *notCreated*: "String[SetError]" A map of creation id to a<br />
SetError object for each Mailbox that failed to be created. The<br />
possible errors are defined above.<br />
<br />
o *notUpdated*: "String[SetError]" A map of Mailbox id to a SetError<br />
object for each Mailbox that failed to be updated. The possible<br />
errors are defined above.<br />
<br />
o *notDestroyed*: "String[SetError]" A map of Mailbox id to a<br />
SetError object for each Mailbox that failed to be destroyed. The<br />
possible errors are defined above.<br />
<br />
The following errors may be returned instead of the _mailboxesSet_<br />
response:<br />
<br />
Jenkins Expires April 22, 2017 [Page 14]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"accountReadOnly": Returned if the account has "isReadOnly == true".<br />
<br />
"requestTooLarge": Returned if the total number of objects to create,<br />
update or destroy exceeds the maximum number the server is willing to<br />
process in a single method call.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A _description_ property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
"stateMismatch": Returned if an "ifInState" argument was supplied and<br />
it does not match the current state.<br />
<br />
Example request:<br />
<br />
[ "setMailboxes", {<br />
"ifInState": "ms4123",<br />
"update": {<br />
"f3": {<br />
"name": "The new name"<br />
}<br />
},<br />
"destroy": [ "f5" ]<br />
}, "#0" ]<br />
<br />
3. MessageLists<br />
<br />
A *MessageList* is a sorted query on the set of messages in a user's<br />
account. Since it can be very long, the client must specify what<br />
section of the list to return. The client can optionally also fetch<br />
the threads and/or messages for this part of the list.<br />
<br />
The same message may appear in multiple messages lists. For example,<br />
it may belong to multiple mailboxes, and of course it can appear in<br />
searches. Since messages have an immutable id, a client can easily<br />
tell if it already has a message cached and only fetch the ones it<br />
needs.<br />
<br />
When the state changes on the server, a delta update can be requested<br />
to efficiently update the client's cache of this list to the new<br />
<br />
Jenkins Expires April 22, 2017 [Page 15]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
state. If the server doesn't support this, the client still only<br />
needs to fetch the message list again, not the messages themselves.<br />
<br />
3.1. getMessageList<br />
<br />
To fetch a message list, make a call to _getMessageList_. It takes<br />
the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", the primary account will be used.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" Determines the set<br />
of messages returned in the results. See the "Filtering" section<br />
below for allowed values and semantics.<br />
<br />
o *sort*: "String[]|null" A list of Message property names to sort<br />
by. See the "Sorting" section below for allowed values and<br />
semantics.<br />
<br />
o *collapseThreads*: "Boolean|null" If true, each thread will only<br />
be returned once in the resulting list, at the position of the<br />
first message in the list (given the filter and sort order)<br />
belonging to the thread. If "false" or "null", threads may be<br />
returned multiple times.<br />
<br />
o *position*: "Number|null" The 0-based index of the first result in<br />
the list to return. If a negative value is given, the call MUST<br />
be rejected with an "invalidArguments" error. If "null", 0 is<br />
used.<br />
<br />
o *anchor*: "String|null" A Message id. The index of this message<br />
id will be used in combination with the "anchorOffset" argument to<br />
determine the index of the first result to return (see the<br />
"Windowing" section below for more details).<br />
<br />
o *anchorOffset*: "Number|null" The index of the anchor message<br />
relative to the index of the first result to return. This MAY be<br />
negative. For example, "-1" means the first message after the<br />
anchor message should be the first result in the results returned<br />
(see the "Windowing" section below for more details).<br />
<br />
o *limit*: "Number|null" The maximum number of results to return.<br />
If "null", no limit is presumed. The server MAY choose to enforce<br />
a maximum "limit" argument. In this case, if a greater value is<br />
given, the limit should be clamped to the maximum; since the total<br />
number of results in the list is returned, the client should not<br />
be relying on how many results are returned to determine if it has<br />
<br />
Jenkins Expires April 22, 2017 [Page 16]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
reached the end of the list. If a negative value is given, the<br />
call MUST be rejected with an "invalidArguments" error.<br />
<br />
o *fetchThreads*: "Boolean|null" If "true", after outputting a<br />
_messageList_ response, an implicit call will be made to<br />
_getThreads_ with the _threadIds_ array in the response as the<br />
_ids_ argument, and the _fetchMessages_ and<br />
_fetchMessageProperties_ arguments passed straight through from<br />
the call to _getMessageList_. If "false" or "null", no implicit<br />
call will be made.<br />
<br />
o *fetchMessages*: "Boolean|null" If "true" and "fetchThreads ==<br />
false", then after outputting a _messageList_ response, an<br />
implicit call will be made to _getMessages_ with the "messageIds"<br />
array in the response as the _ids_ argument, and the<br />
_fetchMessageProperties_ argument as the _properties_ argument.<br />
If "false" or "null", no implicit call will be made.<br />
<br />
o *fetchMessageProperties*: "String[]|null" The list of properties<br />
to fetch on any fetched messages. See _getMessages_ for a full<br />
description.<br />
<br />
o *fetchSearchSnippets*: "Boolean|null" If "true", then after<br />
outputting a _messageList_ and making any other implicit calls, an<br />
implicit call will be made to _getSearchSnippets_. The<br />
_messageIds_ array from the response will be used as the<br />
_messageIds_ argument, and the _filter_ argument will be passed<br />
straight through. If "false" or "null", no implicit call will be<br />
made.<br />
<br />
3.1.1. Filtering<br />
<br />
A *FilterOperator* object has the following properties:<br />
<br />
o *operator*: "String" This MUST be one of the following strings:<br />
"AND"/"OR"/"NOT":<br />
<br />
* *AND*: all of the conditions must match for the filter to<br />
match.<br />
<br />
* *OR*: at least one of the conditions must match for the filter<br />
to match.<br />
<br />
* *NOT*: none of the conditions must match for the filter to<br />
match.<br />
<br />
o *conditions*: "(FilterCondition|FilterOperator)[]" The conditions<br />
to evaluate against each message.<br />
<br />
Jenkins Expires April 22, 2017 [Page 17]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
A *FilterCondition* object has the following properties:<br />
<br />
o *inMailboxes*: "String[]|null" A list of mailbox ids. A message<br />
must be in ALL of these mailboxes to match the condition.<br />
<br />
o *notInMailboxes*: "String[]|null" A list of mailbox ids. A<br />
message must NOT be in ANY of these mailboxes to match the<br />
condition.<br />
<br />
o *before*: "Date|null" The date of the message (as returned on the<br />
Message object) must be before this date to match the condition.<br />
<br />
o *after*: "Date|null" The date of the message (as returned on the<br />
Message object) must be on or after this date to match the<br />
condition.<br />
<br />
o *minSize*: "Number|null" The size of the message in bytes (as<br />
returned on the Message object) must be equal to or greater than<br />
this number to match the condition.<br />
<br />
o *maxSize*: "Number|null" The size of the message in bytes (as<br />
returned on the Message object) must be less than this number to<br />
match the condition.<br />
<br />
o *threadIsFlagged*: "Boolean|null" If "true", the condition is<br />
matched if the "isFlagged" property of _any_ message in the same<br />
thread as the message being examined is "true". If "false", the<br />
"isFlagged" property of _every_ message in the same thread as the<br />
message being examined must be "false" to match the condition.<br />
<br />
o *threadIsUnread*: "Boolean|null" If "true", the condition is<br />
matched if the "isUnread" property of _any_ message in the same<br />
thread as the message being examined is "true". If "false", the<br />
"isUnread" property of _every_ message in the same thread as the<br />
message being examined must be "false" to match the condition.<br />
<br />
o *isFlagged*: "Boolean|null" The "isFlagged" property of the<br />
message must be identical to the value given to match the<br />
condition.<br />
<br />
o *isUnread*: "Boolean|null" The "isUnread" property of the message<br />
must be identical to the value given to match the condition.<br />
<br />
o *isAnswered*: "Boolean|null" The "isAnswered" property of the<br />
message must be identical to the value given to match the<br />
condition.<br />
<br />
Jenkins Expires April 22, 2017 [Page 18]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *isDraft*: "Boolean|null" The "isDraft" property of the message<br />
must be identical to the value given to match the condition.<br />
<br />
o *hasAttachment*: "Boolean|null" The "hasAttachment" property of<br />
the message must be identical to the value given to match the<br />
condition.<br />
<br />
o *text*: "String|null" Looks for the text in the _from_, _to_,<br />
_cc_, _bcc_, _subject_, _textBody_ or _htmlBody_ properties of the<br />
message.<br />
<br />
o *from*: "String|null" Looks for the text in the _from_ property of<br />
the message.<br />
<br />
o *to*: "String|null" Looks for the text in the _to_ property of the<br />
message.<br />
<br />
o *cc*: "String|null" Looks for the text in the _cc_ property of the<br />
message.<br />
<br />
o *bcc*: "String|null" Looks for the text in the _bcc_ property of<br />
the message.<br />
<br />
o *subject*: "String|null" Looks for the text in the _subject_<br />
property of the message.<br />
<br />
o *body*: "String|null" Looks for the text in the _textBody_ or<br />
_htmlBody_ property of the message.<br />
<br />
o *header*: "String[]|null" The array MUST contain either one or two<br />
elements. The first element is the name of the header to match<br />
against. The second (optional) element is the text to look for in<br />
the header. If not supplied, the message matches simply if it<br />
_has_ a header of the given name.<br />
<br />
If zero properties are specified on the FilterCondition, the<br />
condition MUST always evaluate to "true". If multiple properties are<br />
specified, ALL must apply for the condition to be "true" (it is<br />
equivalent to splitting the object into one-property conditions and<br />
making them all the child of an AND filter operator).<br />
<br />
The exact semantics for matching "String" fields is *deliberately not<br />
defined* to allow for flexibility in indexing implementation, subject<br />
to the following:<br />
<br />
o Text SHOULD be matched in a case-insensitive manner.<br />
<br />
Jenkins Expires April 22, 2017 [Page 19]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o Text contained in either (but matched) single or double quotes<br />
SHOULD be treated as a *phrase search*, that is a match is<br />
required for that exact sequence of words, excluding the<br />
surrounding quotation marks. Use "\"", "\'" and "\\" to match a<br />
literal """, "'" and "\" respectively in a phrase.<br />
<br />
o Outside of a phrase, white-space SHOULD be treated as dividing<br />
separate tokens that may be searched for separately in the<br />
message, but MUST all be present for the message to match the<br />
filter.<br />
<br />
o Tokens MAY be matched on a whole-word basis using stemming (so for<br />
example a text search for "bus" would match "buses" but not<br />
"business").<br />
<br />
o When searching inside the _htmlBody_ property, HTML tags and<br />
attributes SHOULD be ignored.<br />
<br />
3.1.2. Sorting<br />
<br />
The "sort" argument lists the properties to compare between two<br />
messages to determine which comes first in the sort. If two messages<br />
have an identical value for the first property, the next property<br />
will be considered and so on. If all properties are the same (this<br />
includes the case where an empty array or "null" is given as the<br />
argument), the sort order is server-dependent, but MUST be stable<br />
between calls to "getMessageList".<br />
<br />
Following the property name there MUST be a space and then either the<br />
string "asc" or "desc" to specify ascending or descending sort for<br />
that property.<br />
<br />
The following properties MUST be supported for sorting:<br />
<br />
o *id* - The id as returned in the Message object.<br />
<br />
o *date* - The date as returned in the Message object.<br />
<br />
The following properties SHOULD be supported for sorting:<br />
<br />
o *size* - The size as returned in the Message object.<br />
<br />
o *from* - This is taken to be either the "name" part of the Emailer<br />
object, or if none then the "email" part of the Emailer object<br />
(see the definition of the from property in the Message object).<br />
If still none, consider the value to be the empty string.<br />
<br />
Jenkins Expires April 22, 2017 [Page 20]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *to* - This is taken to be either the "name" part of the *first*<br />
Emailer object, or if none then the "email" part of the *first*<br />
Emailer object (see the definition of the to property in the<br />
Message object). If still none, consider the value to be the<br />
empty string.<br />
<br />
o *subject* - This is taken to be the subject of the Message with<br />
any ignoring any leading "Fwd:"s or "Re:"s (case-insensitive<br />
match).<br />
<br />
o *threadIsFlagged* - This value MUST be considered "true" for the<br />
message if *any* of the messages in the same thread (regardless of<br />
mailbox) have "isFlagged: true".<br />
<br />
o *threadIsUnread* - This value MUST be considered "true" for the<br />
message if *any* of the messages in the same thread (regardless of<br />
mailbox) have "isUnread: true".<br />
<br />
o *isFlagged* - The "isFlagged" state of the message (only).<br />
<br />
o *isUnread* - The "isUnread" state of the message (only).<br />
<br />
The server MAY support sorting based on other properties as well. A<br />
client can discover which properties are supported by inspecting the<br />
server's _capabilities_ object (see section 1).<br />
<br />
The method of comparison depends on the type of the property:<br />
<br />
o "String": Comparison function is server-dependent. It SHOULD be<br />
case-insensitive and SHOULD take into account locale-specific<br />
conventions if known for the user. However, the server MAY choose<br />
to just sort based on unicode code point, after best-effort<br />
translation to lower-case.<br />
<br />
o "Date": If sorting in ascending order, the earlier date MUST come<br />
first.<br />
<br />
o "Boolean": If sorting in ascending order, a "false" value MUST<br />
come before a "true" value.<br />
<br />
3.1.3. Thread collapsing<br />
<br />
When "collapseThreads == true", then after filtering and sorting the<br />
message list, the list is further winnowed by removing any messages<br />
for a thread id that has already been seen (when passing through the<br />
list sequentially). A thread will therefore only appear *once* in<br />
the "threadIds" list of the result, at the position of the first<br />
message in the list that belongs to the thread.<br />
<br />
Jenkins Expires April 22, 2017 [Page 21]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
3.1.4. Windowing<br />
<br />
If a _position_ offset is supplied, then this is the 0-based index of<br />
the first result to return in the list of messages after filtering,<br />
sorting and collapsing threads. If the index is greater than or<br />
equal to the total number of messages in the list, then there are no<br />
results to return, but this DOES NOT generate an error. If<br />
_position_ is "null" (or, equivalently, omitted) this MUST be<br />
interpreted as "position: 0".<br />
<br />
Alternatively, a message id, called the *anchor* may be given. In<br />
this case, after filtering, sorting and collapsing threads, the<br />
anchor is searched for in the message list. If found, the *anchor<br />
offset* is then subtracted from this index. If the resulting index<br />
is now negative, it is clamped to 0. This index is now used exactly<br />
as though it were supplied as the "position" argument. If the anchor<br />
is not found, the call is rejected with an "anchorNotFound" error.<br />
<br />
If an _anchor_ is specified, any position argument supplied by the<br />
client MUST be ignored. If _anchorOffset_ is "null", it defaults to<br />
"0". If no _anchor_ is supplied, any anchor offset argument MUST be<br />
ignored.<br />
<br />
3.1.5. Response<br />
<br />
The response to a call to _getMessageList_ is called _messageList_.<br />
It has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" The filter of the<br />
message list. Echoed back from the call.<br />
<br />
o *sort*: "String[]|null" A list of Message property names used to<br />
sort by. Echoed back from the call.<br />
<br />
o *collapseThreads*: "Boolean|null" Echoed back from the call.<br />
<br />
o *state*: "String" A string encoding the current state on the<br />
server. This string will change if the results of the message<br />
list MAY have changed (for example, there has been a change to the<br />
state of the set of Messages; it does not guarantee that anything<br />
in the list has changed). It may be passed to<br />
_getMessageListUpdates_ to efficiently get the set of changes from<br />
the previous state. Should a client receive back a response with<br />
a different state string to a previous call, it MUST either throw<br />
away the currently cached list and fetch it again (note, this does<br />
not require fetching the messages again, just the list of ids) or,<br />
<br />
Jenkins Expires April 22, 2017 [Page 22]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
if the server supports it, call _getMessageListUpdates_ to get the<br />
delta difference.<br />
<br />
o *canCalculateUpdates*: "Boolean" This is "true" if the server<br />
supports calling "getMessageListUpdates" with these<br />
"filter"/"sort"/"collapseThreads" parameters. Note, this does not<br />
guarantee that the getMessageListUpdates call will succeed, as it<br />
may only be possible for a limited time afterwards due to server<br />
internal implementation details.<br />
<br />
o *position*: "Number" The 0-based index of the first result in the<br />
"threadIds" array within the complete list.<br />
<br />
o *total*: "Number" The total number of messages in the message list<br />
(given the _filter_ and _collapseThreads_ arguments).<br />
<br />
o *threadIds*: "String[]" The list of Thread ids for each message in<br />
the list after filtering, sorting and collapsing threads, starting<br />
at the index given by the _position_ argument of this response,<br />
and continuing until it hits the end of the list or reaches the<br />
"limit" number of ids.<br />
<br />
o *messageIds*: "String[]" The list of Message ids for each message<br />
in the list after filtering, sorting and collapsing threads,<br />
starting at the index given by the _position_ argument of this<br />
response, and continuing until it hits the end of the list or<br />
reaches the "limit" number of ids.<br />
<br />
The following errors may be returned instead of the "messageList"<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"unsupportedSort": Returned if the _sort_ includes a property the<br />
server does not support sorting on.<br />
<br />
"cannotDoFilter": Returned if the server is unable to process the<br />
given _filter_ for any reason.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A "description" property MAY be present on the<br />
<br />
Jenkins Expires April 22, 2017 [Page 23]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"anchorNotFound": Returned if an anchor argument was supplied, but it<br />
cannot be found in the message list.<br />
<br />
3.2. getMessageListUpdates<br />
<br />
The "getMessageListUpdates" call allows a client to efficiently<br />
update the state of any cached message list to match the new state on<br />
the server. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", the primary account will be used.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" The filter<br />
argument that was used with _getMessageList_.<br />
<br />
o *sort*: "String[]|null" The sort argument that was used with<br />
_getMessageList_.<br />
<br />
o *collapseThreads*: "Boolean|null" The _collapseThreads_ argument<br />
that was used with _getMessageList_.<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the<br />
_messageList_ response. The server will return the changes made<br />
since this state.<br />
<br />
o *uptoMessageId*: "String|null" The message id of the last message<br />
in the list that the client knows about. In the common case of<br />
the client only having the first X ids cached, this allows the<br />
server to ignore changes further down the list the client doesn't<br />
care about.<br />
<br />
o *maxChanges*: "Number|null" The maximum number of changes to<br />
return in the response. See below for a more detailed<br />
description.<br />
<br />
The response to _getMessageListUpdates_ is called<br />
_messageListUpdates_ It has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" The filter of the<br />
message list. Echoed back from the call.<br />
<br />
Jenkins Expires April 22, 2017 [Page 24]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *sort*: "String[]|null" A list of Message property names used to<br />
sort by. Echoed back from the call.<br />
<br />
o *collapseThreads*: "Boolean|null" Echoed back from the call.<br />
<br />
o *oldState*: "String" This is the "sinceState" argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *uptoMessageId*: "String|null" Echoed back from the call.<br />
<br />
o *total*: "Number" The total number of messages in the message list<br />
(given the filter and collapseThreads arguments).<br />
<br />
o *removed*: "RemovedItem[]" The _messageId_ and _threadId_ for<br />
every message that was in the list in the old state and is not in<br />
the list in the new state. If the server cannot calculate this<br />
exactly, the server MAY return extra messages in addition that MAY<br />
have been in the old list but are not in the new list. If an<br />
_uptoMessageId_ was given AND this id was found in the list, only<br />
messages positioned before this message that were removed need be<br />
returned. In addition, if the sort includes the property<br />
_isUnread_ or _isFlagged_, the server MUST include all messages in<br />
the current list for which this property MAY have changed. If<br />
"collapseThreads == true", then the server MUST include all<br />
messages in the current list for which this property MAY have<br />
changed *on any of the messages in the thread*.<br />
<br />
o *added*: "AddedItem[]" The messageId and threadId and index in the<br />
list (in the new state) for every message that has been added to<br />
the list since the old state AND every message in the current list<br />
that was included in the _removed_ array (due to a filter or sort<br />
based upon a mutable property). The array MUST be sorted in order<br />
of index, lowest index first. If an _uptoMessageId_ was given AND<br />
this id was found in the list, only messages positioned before<br />
this message that have been added need be returned.<br />
<br />
A *RemovedItem* object has the following properties:<br />
<br />
o *messageId*: "String"<br />
<br />
o *threadId*: "String"<br />
<br />
An *AddedItem* object has the following properties:<br />
<br />
o *messageId*: "String"<br />
<br />
Jenkins Expires April 22, 2017 [Page 25]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *threadId*: "String"<br />
<br />
o *index*: "Number"<br />
<br />
The result of this should be that if the client has a cached sparse<br />
array of message ids in the list in the old state:<br />
<br />
messageIds = [ "id1", "id2", null, null, "id3", "id4", null, null, null ]<br />
<br />
then if it *splices out* all messages in the removed array:<br />
<br />
removed = [{ messageId: "id2", ... }];<br />
messageIds => [ "id1", null, null, "id3", "id4", null, null, null ]<br />
<br />
and *splices in* (in order) all of the messages in the added array:<br />
<br />
added = [{ messageId: "id5", index: 0, ... }];<br />
messageIds => [ "id5", "id1", null, null, "id3", "id4", null, null, null ]<br />
<br />
then the message list will now be in the new state.<br />
<br />
The following errors may be returned instead of the<br />
"messageListUpdates" response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"tooManyChanges": Returned if there are more changes the the client's<br />
_maxChanges_ argument. Each item in the removed or added array is<br />
considered as one change. The client may retry with a higher max<br />
changes or invalidate its cache of the message list.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old. The client MUST invalidate its<br />
cache of the message list.<br />
<br />
Jenkins Expires April 22, 2017 [Page 26]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
4. Threads<br />
<br />
Replies are grouped together with the original message to form a<br />
thread. In JMAP, a thread is simply a flat list of messages, ordered<br />
by date. Every message MUST belong to a thread, even if it is the<br />
only message in the thread.<br />
<br />
The JMAP spec does not require the server to use any particular<br />
algorithm for determining whether two messages belong to the same<br />
thread, however there is a recommended algorithm in the<br />
implementation guide [1].<br />
<br />
If messages are delivered out of order for some reason, a user may<br />
receive two messages in the same thread but without headers that<br />
associate them with each other. The arrival of a third message in<br />
the thread may provide the missing references to join them all<br />
together into a single thread. Since the "threadId" of a message is<br />
immutable, if the server wishes to merge the threads, it MUST handle<br />
this by deleting and reinserting (with a new message id) the messages<br />
that change threadId.<br />
<br />
A *Thread* object has the following properties:<br />
<br />
o *id*: "String" The id of the thread. This property is immutable.<br />
<br />
o *messageIds*: "String[]" The ids of the messages in the thread,<br />
sorted such that:<br />
<br />
* Any message with "isDraft == true" and an _inReplyToMessageId_<br />
property that corresponds to another message in the thread<br />
comes immediately after that message in the sort order.<br />
<br />
* Other than that, everything is sorted in date order (the same<br />
as the _date_ property on the Message object), oldest first.<br />
<br />
4.1. getThreads<br />
<br />
Threads can only be fetched explicitly by id. To fetch threads, make<br />
a call to _getThreads_. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
o *ids*: "String[]" An array of ids for the threads to fetch.<br />
<br />
o *fetchMessages*: "Boolean|null" If true, after outputting a<br />
_threads_ response, an implicit call will be made to _getMessages_<br />
with a list of all message ids in the returned threads as the<br />
<br />
Jenkins Expires April 22, 2017 [Page 27]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
_ids_ argument, and the _fetchMessageProperties_ argument as the<br />
_properties_ argument. If "false" or "null", no implicit call<br />
will be made.<br />
<br />
o *fetchMessageProperties*: "String[]|null" The list of properties<br />
to fetch on any fetched messages. See _getMessages_ for a full<br />
description.<br />
<br />
The response to _getThreads_ is called _threads_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *state*: "String" A string encoding the current state on the<br />
server. This string will change if any threads change (that is,<br />
new messages arrive, or messages are deleted, as these are the<br />
only two events that change thread membership). It can be passed<br />
to _getThreadUpdates_ to efficiently get the list of changes from<br />
the previous state.<br />
<br />
o *list*: "Thread[]" An array of Thread objects for the requested<br />
thread ids. This may not be in the same order as the ids were in<br />
the request.<br />
<br />
o *notFound*: "String[]|null" An array of thread ids requested which<br />
could not be found, or "null" if all ids were found.<br />
<br />
The following errors may be returned instead of the "threads"<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"requestTooLarge": Returned if the number of _ids_ requested by the<br />
client exceeds the maximum number the server is willing to process in<br />
a single method call.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
Example of a successful request:<br />
<br />
Jenkins Expires April 22, 2017 [Page 28]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
[ "getThreads", {<br />
"ids": ["f123u4", "f41u44"],<br />
"fetchMessages": false,<br />
"fetchMessageProperties": null<br />
}, "#1" ]<br />
<br />
and response:<br />
<br />
[ "threads", {<br />
"state": "f6a7e214",<br />
"list": [<br />
{<br />
"id": "f123u4",<br />
"messageIds": [ "eaa623", "f782cbb"]<br />
},<br />
{<br />
"id": "f41u44",<br />
"messageIds": [ "82cf7bb" ]<br />
}<br />
],<br />
"notFound": null<br />
}, "#1" ]<br />
<br />
4.2. getThreadUpdates<br />
<br />
When messages are created or deleted, new threads may be created, or<br />
the set of messages belonging to an existing thread may change. If a<br />
call to _getThreads_ returns with a different _state_ string in the<br />
response to a previous call, the state of the threads has changed on<br />
the server and the client needs to work out which part of its cache<br />
is now invalid.<br />
<br />
The _getThreadUpdates_ call allows a client to efficiently update the<br />
state of any cached threads to match the new state on the server. It<br />
takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the<br />
_threads_ response. The server will return the changes made since<br />
this state.<br />
<br />
o *maxChanges*: "Number|null" The maximum number of Thread ids to<br />
return in the response. The server MAY choose to clamp this value<br />
to a particular maximum or set a maximum if none is given by the<br />
client. If supplied by the client, the value MUST be a positive<br />
<br />
Jenkins Expires April 22, 2017 [Page 29]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
integer greater than 0. If a value outside of this range is<br />
given, the server MUST reject the call with an "invalidArguments"<br />
error.<br />
<br />
o *fetchRecords*: "Boolean|null" If "true", immediately after<br />
outputting a _threadUpdates_ response, an implicit call will be<br />
made to _getThreads_ with the _changed_ property of the response<br />
as the _ids_ argument, and _fetchMessages_ equal to "false".<br />
<br />
The response to _getThreadUpdates_ is called _threadUpdates_. It has<br />
the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String" This is the _sinceState_ argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *hasMoreUpdates*: "Boolean" If "true", the client may call<br />
_getThreadUpdates_ again with the _newState_ returned to get<br />
further updates. If "false", _newState_ is the current server<br />
state.<br />
<br />
o *changed*: "String[]" An array of thread ids where the list of<br />
messages within the thread has changed between the old state and<br />
the new state, and the thread currently has at least one message<br />
in it.<br />
<br />
o *removed*: "String[]" An array of thread ids where the list of<br />
messages within the thread has changed since the old state, and<br />
there are now no messages in the thread.<br />
<br />
If a _maxChanges_ is supplied, or set automatically by the server,<br />
the server MUST ensure the number of ids returned across _changed_<br />
and _removed_ does not exceed this limit. If there are more changes<br />
than this between the client's state and the current server state,<br />
the update returned SHOULD generate an update to take the client to<br />
an intermediate state, from which the client can continue to call<br />
_getThreadUpdates_ until it is fully up to date. If it is unable to<br />
calculat an intermediate state, it MUST return a<br />
"cannotCalculateChanges" error response instead.<br />
<br />
If a thread has been modified AND deleted since the oldState, the<br />
server SHOULD just return the id in the _removed_ response, but MAY<br />
return it in the changed response as well. If a thread has been<br />
created AND deleted since the oldState, the server SHOULD remove the<br />
<br />
Jenkins Expires April 22, 2017 [Page 30]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
thread id from the response entirely, but MAY include it in the<br />
_removed_ response.<br />
<br />
The following errors may be returned instead of the _threadUpdates_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old, or the server being unable to<br />
produce an update to an intermediate state when there are too many<br />
updates. The client MUST invalidate its Thread cache.<br />
<br />
5. Messages<br />
<br />
Just like in IMAP, a message is *immutable* except for the boolean<br />
"isXXX" status properties and the set of mailboxes it is in. This<br />
allows for more efficient caching of messages, and gives easier<br />
backwards compatibility for servers implementing an IMAP interface to<br />
the same data.<br />
<br />
JMAP completely hides the complexities of MIME. All special<br />
encodings of either headers or the body, such as base64 [2], or RFC<br />
2047 [3] encoding of non-ASCII characters, MUST be fully decoded into<br />
standard UTF-8.<br />
<br />
A *Message* object has the following properties:<br />
<br />
o *id*: "String" The id of the message.<br />
<br />
o *blobId*: "String" The id representing the raw [RFC5322] message.<br />
This may be used to download the original message or to attach it<br />
directly to another message etc.<br />
<br />
o *threadId*: "String" The id of the thread to which this message<br />
belongs.<br />
<br />
Jenkins Expires April 22, 2017 [Page 31]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *mailboxIds*: "String[]" (Mutable) The ids of the mailboxes the<br />
message is in. A message MUST belong to one or more mailboxes at<br />
all times (until it is deleted).<br />
<br />
o *inReplyToMessageId*: "String|null" The id of the Message this<br />
message is a reply to. This is primarily for drafts, but the<br />
server MAY support this for received messages as well by looking<br />
up the [RFC5322] Message-Id referenced in the "In-Reply-To" header<br />
and searching for this message in the user's mail.<br />
<br />
o *isUnread*: "Boolean" (Mutable) Has the message not yet been read?<br />
This corresponds to the *opposite* of the "\Seen" system flag in<br />
IMAP.<br />
<br />
o *isFlagged*: "Boolean" (Mutable) Is the message flagged? This<br />
corresponds to the "\Flagged" system flag in IMAP.<br />
<br />
o *isAnswered*: "Boolean" (Mutable) Has the message been replied to?<br />
This corresponds to the "\Answered" system flag in IMAP.<br />
<br />
o *isDraft*: "Boolean" (Mutable by the server only) Is the message a<br />
draft? This corresponds to the "\Draft" system flag in IMAP.<br />
<br />
o *hasAttachment*: "Boolean" Does the message have any attachments?<br />
<br />
o *headers*: "String[String]" A map of lower-cased header name to<br />
(decoded) header value for all headers in the message. For<br />
headers that occur multiple times (e.g. "Received"), the values<br />
are concatenated with a single new line ("\n") character in<br />
between each one.<br />
<br />
o *sender*: "Emailer|null" An Emailer object (see below) containing<br />
the name/email from the parsed "Sender" header of the email. If<br />
the email doesn't have a "Sender" header, this is "null".<br />
<br />
o *from*: "Emailer[]|null" An array of name/email objects (see<br />
below) representing the parsed "From" header of the email, in the<br />
same order as they appear in the header. If the email doesn't<br />
have a "From" header, this is "null". If the header exists but<br />
does not have any content, the response is an array of zero<br />
length.<br />
<br />
o *to*: "Emailer[]|null" An array of name/email objects (see below)<br />
representing the parsed "To" header of the email, in the same<br />
order as they appear in the header. If the email doesn't have a<br />
"To" header, this is "null". If the header exists but does not<br />
have any content, the response is an array of zero length.<br />
<br />
Jenkins Expires April 22, 2017 [Page 32]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *cc*: "Emailer[]|null" An array of name/email objects (see below)<br />
representing the parsed "Cc" header of the email, in the same<br />
order as they appear in the header. If the email doesn't have a<br />
"Cc" header, this is "null". If the header exists but does not<br />
have any content, the response is an array of zero length.<br />
<br />
o *bcc*: "Emailer[]|null" An array of name/email objects (see below)<br />
representing the parsed "Bcc" header of the email. If the email<br />
doesn't have a "Bcc" header (which will be true for most emails<br />
outside of the Sent mailbox), this is "null". If the header<br />
exists but does not have any content, the response is an array of<br />
zero length.<br />
<br />
o *replyTo*: "Emailer[]|null" An array of name/email objects (see<br />
below) representing the parsed "Reply-To" header of the email, in<br />
the same order as they appear in the header. If the email doesn't<br />
have a "Reply-To" header, this is "null". If the header exists<br />
but does not have any content, the response is an array of zero<br />
length.<br />
<br />
o *subject*: "String" The subject of the message.<br />
<br />
o *date*: "Date" The date the message was sent (or saved, if the<br />
message is a draft).<br />
<br />
o *size*: "Number" The size in bytes of the whole message as counted<br />
by the server towards the user's quota.<br />
<br />
o *preview*: "String" Up to 256 characters of the beginning of a<br />
plain text version of the message body. This is intended to be<br />
shown as a preview line on a mailbox listing, and the server may<br />
choose to skip quoted sections or salutations to return a more<br />
useful preview.<br />
<br />
o *textBody*: "String" The plain text body part for the message. If<br />
there is only an HTML version of the body, a plain text version<br />
MUST be generated from this; the exact method of conversion in<br />
this case is not defined and is server-specific.<br />
<br />
o *htmlBody*: "String|null" The HTML body part for the message if<br />
present.<br />
<br />
o *attachments*: "Attachment[]|null" An array of attachment objects<br />
(see below) detailing all the attachments to the message.<br />
<br />
o *attachedMessages*: "String[Message]|null" An object mapping<br />
attachment id (as found in the "attachments" property) to a<br />
<br />
Jenkins Expires April 22, 2017 [Page 33]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
*Message* object with the following properties, for each [RFC5322]<br />
message attached to this one:<br />
<br />
* headers<br />
<br />
* from<br />
<br />
* to<br />
<br />
* cc<br />
<br />
* bcc<br />
<br />
* replyTo<br />
<br />
* subject<br />
<br />
* date<br />
<br />
* textBody<br />
<br />
* htmlBody<br />
<br />
* attachments<br />
<br />
* attachedMessages<br />
<br />
An *Emailer* object has the following properties:<br />
<br />
o *name*: "String" The name of the sender/recipient. If a name<br />
cannot be extracted for an email, this property SHOULD be the<br />
empty string.<br />
<br />
o *email*: "String" The email address of the sender/recipient. This<br />
MUST be of the form ""<mailbox>@<host>"" If a "host" or even<br />
"mailbox" cannot be extracted for an email, the empty string<br />
SHOULD be used for this part (so the result MUST always still<br />
contain an ""@"" character).<br />
<br />
Group information and comments from the RFC 5322 header MUST be<br />
discarded when converting into an Emailer object.<br />
<br />
Example array of Emailer objects:<br />
<br />
Jenkins Expires April 22, 2017 [Page 34]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
[<br />
{name:"Joe Bloggs", email:"joeb@example.com"},<br />
{name:"", email:"john@example.com"},<br />
{name:"John Smith", email: "john@"}<br />
]<br />
<br />
An *Attachment* object has the following properties:<br />
<br />
o *blobId*: "String" The id of the binary data.<br />
<br />
o *type*: "String" The content-type of the attachment.<br />
<br />
o *name*: "String|null" The full file name, e.g.<br />
"myworddocument.doc", if available.<br />
<br />
o *size*: "Number" The size, in bytes, of the attachment when fully<br />
decoded (i.e. the number of bytes in the file the user would<br />
download).<br />
<br />
o *cid*: "String|null" The id used within the message body to<br />
reference this attachment. This is only unique when paired with<br />
the message id, and has no meaning without reference to that.<br />
<br />
o *isInline*: "Boolean" True if the attachment is referenced by a<br />
"cid:" link from within the HTML body of the message.<br />
<br />
o *width*: "Number|null" The width (in px) of the image, if the<br />
attachment is an image.<br />
<br />
o *height*: "Number|null" The height (in px) of the image, if the<br />
attachment is an image.<br />
<br />
5.1. getMessages<br />
<br />
Messages can only be fetched explicitly by id. To fetch messages,<br />
make a call to "getMessages". It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
o *ids*: "String[]" An array of ids for the messages to fetch.<br />
<br />
o *properties*: "String[]|null" A list of properties to fetch for<br />
each message. If "null", all properties will be fetched.<br />
<br />
The "id" property is always returned, regardless of whether it is in<br />
the list of requested properties. The possible values for<br />
"properties" can be found above in the description of the Message<br />
<br />
Jenkins Expires April 22, 2017 [Page 35]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
object. In addition to this, the client may request the following<br />
special values:<br />
<br />
o *body*: If ""body"" is included in the list of requested<br />
properties, it will be interpreted by the server as a request for<br />
""htmlBody"" if the message has an HTML part, or ""textBody""<br />
otherwise.<br />
<br />
o *headers.property*: Instead of requesting all the headers (by<br />
requesting the ""headers"" property, the client may specify the<br />
particular headers it wants using the "headers.property-name"<br />
syntax, e.g. ""headers.x-spam-score", "headers.x-spam-hits"").<br />
The server will return a _headers_ property but with just the<br />
requested headers in the object rather than all headers. If<br />
""headers"" is requested, the server MUST ignore the individual<br />
header requests and just return all headers. If a requested<br />
header is not present in the message, it MUST not be present in<br />
the _headers_ object. Header names are case-insensitive.<br />
<br />
The response to _getMessages_ is called _messages_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *state*: "String" A string encoding the current state on the<br />
server. This string will change if any messages change (that is,<br />
a new message arrives, a change is made to one of the mutable<br />
properties, or a message is deleted). It can be passed to<br />
_getMessageUpdates_ to efficiently get the list of changes from<br />
the previous state.<br />
<br />
o *list*: "Message[]" An array of Message objects for the requested<br />
message ids. This may not be in the same order as the ids were in<br />
the request.<br />
<br />
o *notFound*: "String[]|null" An array of message ids requested<br />
which could not be found, or "null" if all ids were found.<br />
<br />
The following errors may be returned instead of the _messages_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
Jenkins Expires April 22, 2017 [Page 36]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
"requestTooLarge": Returned if the number of _ids_ requested by the<br />
client exceeds the maximum number the server is willing to process in<br />
a single method call.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
Example request:<br />
<br />
["getMessages", {<br />
"ids": [ "f123u456", "f123u457" ],<br />
"properties": [ "threadId", "mailboxIds", "from", "subject", "date" ]<br />
}, "#1"]<br />
<br />
and response:<br />
<br />
["messages", {<br />
"state": "41234123231",<br />
"list": [<br />
{<br />
messageId: "f123u457",<br />
threadId: "ef1314a",<br />
mailboxIds: [ "f123" ],<br />
from: [{name: "Joe Bloggs", email: "joe@bloggs.com"}],<br />
subject: "Dinner on Thursday?",<br />
date: "2013-10-13T14:12:00Z"<br />
}<br />
],<br />
notFound: [ "f123u456" ]<br />
}, "#1"]<br />
<br />
5.2. getMessageUpdates<br />
<br />
If a call to _getMessages_ returns with a different _state_ string in<br />
the response to a previous call, the state of the messages has<br />
changed on the server. For example, a new message may have been<br />
delivered, or an existing message may have changed mailboxes.<br />
<br />
The _getMessageUpdates_ call allows a client to efficiently update<br />
the state of any cached messages to match the new state on the<br />
server. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
Jenkins Expires April 22, 2017 [Page 37]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the<br />
_messages_ response. The server will return the changes made<br />
since this state.<br />
<br />
o *maxChanges*: "Number|null" The maximum number of changed messages<br />
to return in the response. The server MAY choose to clamp this<br />
value to a particular maximum or set a maximum if none is given by<br />
the client. If supplied by the client, the value MUST be a<br />
positive integer greater than 0. If a value outside of this range<br />
is given, the server MUST reject the call with an<br />
"invalidArguments" error.<br />
<br />
o *fetchRecords*: "Boolean|null" If true, immediately after<br />
outputting a _messageUpdates_ response, an implicit call will be<br />
made to _getMessages_ with a list of all message ids in the<br />
_changed_ argument of the response as the _ids_ argument, and the<br />
_fetchRecordProperties_ argument as the _properties_ argument.<br />
<br />
o *fetchRecordProperties*: "String[]|null" The list of properties to<br />
fetch on any fetched messages. See _getMessages_ for a full<br />
description.<br />
<br />
The response to _getMessageUpdates_ is called _messageUpdates_. It<br />
has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String" This is the _sinceState_ argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *hasMoreUpdates*: "Boolean" If "true", the client may call<br />
_getMessageUpdates_ again with the _newState_ returned to get<br />
further updates. If "false", _newState_ is the current server<br />
state.<br />
<br />
o *changed*: "String[]" An array of message ids for messages that<br />
have either been created or had their state change, and are not<br />
currently deleted.<br />
<br />
o *removed*: "String[]" An array of message ids for messages that<br />
have been deleted since the oldState.<br />
<br />
If a _maxChanges_ is supplied, or set automatically by the server,<br />
the server MUST ensure the number of ids returned across _changed_<br />
<br />
Jenkins Expires April 22, 2017 [Page 38]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
and _removed_ does not exceed this limit. If there are more changes<br />
than this between the client's state and the current server state,<br />
the update returned SHOULD generate an update to take the client to<br />
an intermediate state, from which the client can continue to call<br />
_getMessageUpdates_ until it is fully up to date. If it is unable to<br />
calculat an intermediate state, it MUST return a<br />
"cannotCalculateChanges" error response instead.<br />
<br />
If a message has been modified AND deleted since the oldState, the<br />
server SHOULD just return the id in the _removed_ response, but MAY<br />
return it in the changed response as well. If a message has been<br />
created AND deleted since the oldState, the server SHOULD remove the<br />
message id from the response entirely, but MAY include it in the<br />
_removed_ response, and (if in the _removed_ response) MAY included<br />
it in the _changed_ response as well.<br />
<br />
The following errors may be returned instead of the _messageUpdates_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old, or the server being unable to<br />
produce an update to an intermediate state when there are too many<br />
updates. The client MUST invalidate its Message cache.<br />
<br />
5.3. setMessages<br />
<br />
The _setMessages_ method encompasses:<br />
<br />
o Creating a draft message<br />
<br />
o Sending a message<br />
<br />
o Changing the flags of a message (unread/flagged status)<br />
<br />
Jenkins Expires April 22, 2017 [Page 39]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o Adding/removing a message to/from mailboxes (moving a message)<br />
<br />
o Deleting messages<br />
<br />
It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
o *ifInState*: "String|null" This is a state string as returned by<br />
the _getMessages_ method. If supplied, the string must match the<br />
current state, otherwise the method will be aborted and a<br />
"stateMismatch" error returned. If "null", any changes will be<br />
applied to the current state.<br />
<br />
o *create*: "String[Message]|null" A map of _creation id_ (an<br />
arbitrary string set by the client) to Message objects (see below<br />
for a detailed description).<br />
<br />
o *update*: "String[Message]|null" A map of message id to objects<br />
containing the properties to update for that Message.<br />
<br />
o *destroy*: "String[]|null" A list of ids for Message objects to<br />
permanently delete.<br />
<br />
Each create, update or destroy is considered an atomic unit. It is<br />
permissible for the server to commit some of the changes but not<br />
others, however it is not permissible to only commit part of an<br />
update to a single record (e.g. update the _isFlagged_ field but not<br />
the _mailboxIds_ field, if both are supplied in the update object for<br />
a message).<br />
<br />
If a create, update or destroy is rejected, the appropriate error<br />
MUST be added to the notCreated/notUpdated/notDestroyed property of<br />
the response and the server MUST continue to the next create/update/<br />
destroy. It does not terminate the method.<br />
<br />
If an id given cannot be found, the update or destroy MUST be<br />
rejected with a "notFound" set error.<br />
<br />
5.3.1. Saving a draft<br />
<br />
Creating messages via the _setMessages_ method is only for creating<br />
draft messages and sending them. For delivering/importing a complete<br />
[RFC5322] message, use the "importMessages" method.<br />
<br />
The properties of the Message object submitted for creation MUST<br />
conform to the following conditions:<br />
<br />
Jenkins Expires April 22, 2017 [Page 40]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *id*: This property MUST NOT be included. It is set by the server<br />
upon creation.<br />
<br />
o *blobId*: This property MUST NOT be included. It is set by the<br />
server upon creation.<br />
<br />
o *threadId*: This property MUST NOT be included. It is set by the<br />
server upon creation.<br />
<br />
o *mailboxIds*: This property MUST be included. The value MUST<br />
include the id of either the mailbox with "role == "drafts"" (to<br />
save a draft) or the mailbox with "role == "outbox"" (to send the<br />
message). If this mailbox does not have "mustBeOnlyMailbox ==<br />
true", others may be included too.<br />
<br />
o *inReplyToMessageId*: Optional. If included, the server will look<br />
up this message and if found set appropriate "References" and "In-<br />
Reply-To" headers. These will override any such headers supplied<br />
in the _headers_ property. If not found, the creation MUST be<br />
rejected with an "inReplyToNotFound" error.<br />
<br />
o *isUnread*: Optional, defaults to "false". If included this MUST<br />
be "false".<br />
<br />
o *isFlagged*: Optional, defaults to "false".<br />
<br />
o *isAnswered*: Optional, defaults to "false". If included this<br />
MUST be "false".<br />
<br />
o *isDraft*: Optional, defaults to "true". If included this MUST be<br />
"true".<br />
<br />
o *hasAttachment*: This property MUST NOT be included. It is set by<br />
the server upon creation based on the attachments property.<br />
<br />
o *headers*: Optional. The keys MUST only contain the characters<br />
a-z (lower-case only), 0-9 and hyphens.<br />
<br />
o *from*: Optional. Overrides a "From" in the _headers_.<br />
<br />
o *to*: Optional. Overrides a "To" in the _headers_.<br />
<br />
o *cc*: Optional. Overrides a "Cc" in the _headers_.<br />
<br />
o *bcc*: Optional. Overrides a "Bcc" in the _headers_.<br />
<br />
o *replyTo*: Optional. Overrides a "Reply-To" in the _headers_.<br />
<br />
Jenkins Expires April 22, 2017 [Page 41]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *subject*: Optional. Defaults to the empty string ("""").<br />
<br />
o *date*: Optional. If included, the server SHOULD wait until this<br />
time to send the message (once moved to the outbox mailbox).<br />
Until it is sent, the send may be cancelled by moving the message<br />
back out of the outbox mailbox. If the date is in the past, the<br />
message must be sent immediately. A client may find out if the<br />
server supports delayed sending by querying the server's<br />
_capabilities_ object (see section 1).<br />
<br />
o *size*: This MUST NOT be included. It is set by the server upon<br />
creation.<br />
<br />
o *preview*: This MUST NOT be included. It is set by the server<br />
upon creation.<br />
<br />
o *textBody*: Optional. If not supplied and an htmlBody is, the<br />
server SHOULD generate a text version for the message from the<br />
HTML body.<br />
<br />
o *htmlBody*: Optional.<br />
<br />
o *attachments*: Optional. An array of Attachment objects detailing<br />
all the attachments to the message. To add an attachment, the<br />
file must first be uploaded using the standard upload mechanism;<br />
this will give the client a blobId that may be used to identify<br />
the file. The "cid" property may be assigned by the client, and<br />
is solely used for matching up with "cid:<id>" links inside the<br />
"htmlBody". The server MAY change the cids upon sending.<br />
<br />
If any of the files specified in _attachments_ cannot be found, the<br />
creation MUST be rejected with an "invalidProperties" error. An<br />
extra property SHOULD be included in the error object called<br />
"attachmentsNotFound", of type "String[]", which SHOULD be an array<br />
of the _blobId_ of every attachment that could not be found on the<br />
server. - *attachedMessages*: This MUST NOT be included.<br />
<br />
All optional properties default to "null" unless otherwise stated.<br />
Where included, properties MUST conform to the type given in the<br />
Message object definition.<br />
<br />
If any of the properties are invalid, the server MUST reject the<br />
create with an "invalidProperties" error. The Error object SHOULD<br />
contain a property called _properties_ of type "String[]" that lists<br />
*all* the properties that were invalid. The object MAY also contain<br />
a _description_ property of type "String" with a user-friendly<br />
description of the problems.<br />
<br />
Jenkins Expires April 22, 2017 [Page 42]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
Other than making sure it conforms to the correct type, the server<br />
MUST NOT attempt to validate from/to/cc/bcc when saved as a draft.<br />
This is to ensure messages can be saved at any point. Validation<br />
occurs when the user tries to send a message.<br />
<br />
If a draft cannot be saved due to the user reaching their maximum<br />
mail storage quota, the creation MUST be rejected with a<br />
"maxQuotaReached" error.<br />
<br />
5.3.2. Updating messages<br />
<br />
Messages are mainly immutable, so to update a draft the client must<br />
create a new message and delete the old one. This ensures that if<br />
the draft is also being edited elsewhere, the two will split into two<br />
different drafts to avoid data loss.<br />
<br />
Only the following properties may be modified:<br />
<br />
o *mailboxIds*: The server MUST reject any attempt to add a message<br />
with "isDraft == false" to the outbox. The server MAY reject<br />
attempts to add a draft message to a mailbox that does not have a<br />
role of "drafts", "outbox" or "templates".<br />
<br />
o *isFlagged*<br />
<br />
o *isUnread*<br />
<br />
o *isAnswered*<br />
<br />
Note, a mailbox id may be a _creation id_ (see "setFoos" for a<br />
description of how this works).<br />
<br />
If any of the properties in the update are invalid (immutable and<br />
different to the current server value, wrong type, invalid value for<br />
the property - like a mailbox id for non-existent mailbox), the<br />
server MUST reject the update with an "invalidProperties" error. The<br />
Error object SHOULD contain a property called _properties_ of type<br />
"String[]" that lists *all* the properties that were invalid. The<br />
object MAY also contain a _description_ property of type "String"<br />
with a user-friendly description of the problems.<br />
<br />
If the _id_ given does not correspond to a Message in the given<br />
account, reject the update with a "notFound" error.<br />
<br />
To *delete a message* to trash, simply change the "mailboxIds"<br />
property so it is now in the mailbox with "role == "trash"". If the<br />
mailbox has the property "mustBeOnlyMailbox == true", it must be<br />
removed from all other mailboxes. Otherwise, leave it in those<br />
<br />
Jenkins Expires April 22, 2017 [Page 43]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
mailboxes so that it will be restored to its previous state if<br />
undeleted.<br />
<br />
5.3.3. Sending messages<br />
<br />
To send a message, either create a new message directly into the<br />
mailbox with "role == "outbox"" or move an existing draft into this<br />
mailbox. At this point the server will check that it has everything<br />
it needs for a valid message. In particular, that it has a valid<br />
"From" address (and the user has permission to use this From<br />
address), it has at least one address to send to, and all addresses<br />
in To/Cc/Bcc are valid email addresses. If it cannot send, it will<br />
reject the creation/update with an "invalidProperties" error. The<br />
Error object SHOULD contain a property called _properties_ of type<br />
"String[]" that lists *all* the properties that were invalid. The<br />
object SHOULD also contain a _description_ property of type "String"<br />
with a user-friendly description of the problems to present to the<br />
user.<br />
<br />
If the message is accepted, the server SHOULD *asynchronously*<br />
schedule the message to be sent *after* this method call is complete<br />
(note, this MAY occur before the next method in the same API request<br />
or after the whole API request is complete). This means that the<br />
"newState" string in the response represents a state where the<br />
message is still in the outbox.<br />
<br />
When the message is sent, the server MUST delete the message from the<br />
*outbox* and SHOULD create a *new* copy of the sent message (with a<br />
new id) in the *sent* mailbox, unless the user has indicated another<br />
preference. If "inReplyToMessageId" was set, the server SHOULD mark<br />
this message as "isAnswered: true" at this point, if found. The<br />
server is responsible for either reporting an error (normally a<br />
"bounce" email), or ensuring delivery of the message to the next hop.<br />
<br />
5.3.4. Cancelling a send<br />
<br />
A message may be moved out of the *outbox* and back to the *drafts*<br />
mailbox using the standard update message mechanism, if it has not<br />
yet been sent at the time the method is called. This MUST cancel the<br />
queued send. If the message has already been sent then it will have<br />
been deleted from the outbox, so the update will fail with a standard<br />
"notFound" error.<br />
<br />
5.3.5. Destroying messages<br />
<br />
If the _id_ given does not correspond to a Message in the given<br />
account, the server MUST reject the destruction with a "notFound"<br />
error.<br />
<br />
Jenkins Expires April 22, 2017 [Page 44]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
Destroying a message removes it from all mailboxes to which it<br />
belonged.<br />
<br />
5.3.6. Response<br />
<br />
The response to _setMessages_ is called _messagesSet_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String|null" The state string that would have been<br />
returned by _getMessages_ before making the requested changes, or<br />
"null" if the server doesn't know what the previous state string<br />
was.<br />
<br />
o *newState*: "String" The state string that will now be returned by<br />
_getMessages_.<br />
<br />
o *created*: "String[Message]" A map of the creation id to an object<br />
containing the _id_, _blobId_, _threadId_, and _size_ properties<br />
for each successfully created Message.<br />
<br />
o *updated*: "String[]" A list of Message ids for Messages that were<br />
successfully updated.<br />
<br />
o *destroyed*: "String[]" A list of Message ids for Messages that<br />
were successfully destroyed.<br />
<br />
o *notCreated*: "String[SetError]" A map of creation id to a<br />
SetError object for each Message that failed to be created. The<br />
possible errors are defined above.<br />
<br />
o *notUpdated*: "String[SetError]" A map of Message id to a SetError<br />
object for each Message that failed to be updated. The possible<br />
errors are defined above.<br />
<br />
o *notDestroyed*: "String[SetError]" A map of Message id to a<br />
SetError object for each Message that failed to be destroyed. The<br />
possible errors are defined above.<br />
<br />
The following errors may be returned instead of the _messagesSet_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
Jenkins Expires April 22, 2017 [Page 45]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"accountReadOnly": Returned if the account has "isReadOnly == true".<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A _description_ property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
"stateMismatch": Returned if an _ifInState_ argument was supplied and<br />
it does not match the current state.<br />
<br />
5.4. importMessages<br />
<br />
The _importMessages_ method adds [RFC5322] messages to a user's set<br />
of messages. The messages must first be uploaded as a file using the<br />
standard upload mechanism. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", defaults to the primary account.<br />
<br />
o *messages*: "String[MessageImport]" A map of creation id (client<br />
specified) to MessageImport objects<br />
<br />
An *MessageImport* object has the following properties:<br />
<br />
o *blobId*: "String" The id representing the raw [RFC5322] message<br />
(see the file upload section).<br />
<br />
o *mailboxIds* "String[]" The ids of the mailbox(es) to assign this<br />
message to.<br />
<br />
o *isUnread*: "Boolean"<br />
<br />
o *isFlagged*: "Boolean"<br />
<br />
o *isAnswered*: "Boolean"<br />
<br />
o *isDraft*: "Boolean"<br />
<br />
If "isDraft == true", the mailboxes MUST include the drafts or outbox<br />
mailbox. Adding to the outbox will send the message, as described in<br />
the _setMessages_ section (it will NOT automatically mark any other<br />
message as _isAnswered_).<br />
<br />
Jenkins Expires April 22, 2017 [Page 46]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
The response to _importMessages_ is called _messagesImported_. It has<br />
the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for this call.<br />
<br />
o *created*: "String[Message]" A map of the creation id to an object<br />
containing the _id_, _blobId_, _threadId_ and _size_ properties<br />
for each successfully imported Message.<br />
<br />
o *notCreated*: "String[SetError]" A map of creation id to a<br />
SetError object for each Message that failed to be created. The<br />
possible errors are defined above.<br />
<br />
The following errors may be returned instead of the _messageImported_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"accountReadOnly": Returned if the account has "isReadOnly == true".<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
"notFound": Returned if the URL given in the "file" argument does not<br />
correspond to an internal file.<br />
<br />
"invalidMailboxes": Returned if one of the mailbox ids cannot be<br />
found, or an invalid combination of mailbox ids is specified.<br />
<br />
"maxQuotaReached": Returned if the user has reached their mail quota<br />
so the message cannot be imported.<br />
<br />
5.5. copyMessages<br />
<br />
The only way to move messages *between* two different accounts is to<br />
copy them using the _copyMessages_ method, then once the copy has<br />
succeeded, delete the original. It takes the following arguments:<br />
<br />
o *fromAccountId*: "String|null" The id of the account to copy<br />
messages from. If "null", defaults to the primary account.<br />
<br />
Jenkins Expires April 22, 2017 [Page 47]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *toAccountId*: "String|null" The id of the account to copy<br />
messages to. If "null", defaults to the primary account.<br />
<br />
o *messages*: "String[MessageCopy]" A map of _creation id_ to a<br />
MessageCopy object.<br />
<br />
A *MessageCopy* object has the following properties:<br />
<br />
o *messageId*: "String" The id of the message to be copied in the<br />
"from" account.<br />
<br />
o *mailboxIds*: "String[]" The ids of the mailboxes (in the "to"<br />
account) to add the copied message to.<br />
<br />
o *isUnread*: "Boolean" The _isUnread_ property for the copy.<br />
<br />
o *isFlagged*: "Boolean" The _isFlagged_ property for the copy.<br />
<br />
o *isAnswered*: "Boolean" The _isAnswered_ property for the copy.<br />
<br />
o *isDraft*: "Boolean" The _isDraft_ property for the copy.<br />
<br />
The "from" account may be the same as the "to" account to copy<br />
messages within an account.<br />
<br />
The response to _copyMessages_ is called _messagesCopied_. It has the<br />
following arguments:<br />
<br />
o *fromAccountId*: "String" The id of the account messages were<br />
copied from.<br />
<br />
o *toAccountId*: "String" The id of the account messages were copied<br />
to.<br />
<br />
o *created*: "String[Message]|null" A map of the creation id to an<br />
object containing the _id_, _blobId_, _threadId_ and _size_<br />
properties for each successfully copied Message.<br />
<br />
o *notCreated*: "String[SetError]|null" A map of creation id to a<br />
SetError object for each Message that failed to be copied, "null"<br />
if none.<br />
<br />
The *SetError* may be one of the following types:<br />
<br />
"notFound": Returned if the messageId given can't be found.<br />
<br />
"invalidMailboxes": Returned if one of the mailbox ids cannot be<br />
found, or an invalid combination of mailbox ids is specified.<br />
<br />
Jenkins Expires April 22, 2017 [Page 48]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
"maxQuotaReached": Returned if the user has reached their mail quota<br />
so the message cannot be copied.<br />
<br />
The following errors may be returned instead of the _messagesCopied_<br />
response:<br />
<br />
"fromAccountNotFound": Returned if a _fromAccountId_ was explicitly<br />
included with the request, but it does not correspond to a valid<br />
account.<br />
<br />
"toAccountNotFound": Returned if a _toAccountId_ was explicitly<br />
included with the request, but it does not correspond to a valid<br />
account.<br />
<br />
"fromAccountNoMail": Returned if the _fromAccountId_ given<br />
corresponds to a valid account, but does not contain any mail data.<br />
<br />
"toAccountNoMail": Returned if the _toAccountId_ given corresponds to<br />
a valid account, but does not contain any mail data.<br />
<br />
"accountReadOnly": Returned if the "to" account has "isReadOnly ==<br />
true".<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
5.6. reportMessages<br />
<br />
Messages can be reported as spam or non-spam to help train the user's<br />
spam filter. This MUST NOT affect the state of the Message objects<br />
(it DOES NOT move a message into or out of the Spam mailbox).<br />
<br />
To report messages, make a call to _reportMessages_. It takes the<br />
following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If not given, defaults to the primary account.<br />
<br />
o *messageIds*: "String[]" The list of ids of messages to report.<br />
<br />
o *asSpam*: "Boolean" If "true", learn these messages as spam. If<br />
"false", learn as non-spam.<br />
<br />
The response to _reportMessages_ is called _messagesReported_. It has<br />
the following arguments:<br />
<br />
Jenkins Expires April 22, 2017 [Page 49]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *accountId*: "String" The id of the account used for this call.<br />
<br />
o *asSpam*: "Boolean" Echoed back from the call<br />
<br />
o *reported*: "String[]" The ids of each message successfully<br />
reported.<br />
<br />
o *notFound*: "String[]|null" An array of message ids requested<br />
which could not be found, or "null" if all ids were found.<br />
<br />
The following errors may be returned instead of the<br />
_messagesReported_ response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"accountReadOnly": Returned if the account has "isReadOnly == true".<br />
<br />
"requestTooLarge": Returned if the total number of objects to create,<br />
update or destroy exceeds the maximum number the server is willing to<br />
process in a single method call.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
6. Identities<br />
<br />
A *Identity* object stores information about an email address (or<br />
domain) the user may send from. It has the following properties:<br />
<br />
o *id*: "String" The id of the identity. This property is<br />
immutable.<br />
<br />
o *name*: "String" The "From" _name_ the client SHOULD use when<br />
creating a new message from this identity.<br />
<br />
o *email*: "String" The "From" email address the client MUST use<br />
when creating a new message from this identity. This property is<br />
immutable. The "email" property MAY alternatively be of the form<br />
"*@example.com", in which case the client may use any valid email<br />
address ending in "@example.com".<br />
<br />
Jenkins Expires April 22, 2017 [Page 50]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *replyTo*: "String" The Reply-To value the client SHOULD set when<br />
creating a new message from this identity.<br />
<br />
o *bcc*: "String" The Bcc value the client SHOULD set when creating<br />
a new message from this identity.<br />
<br />
o *textSignature*: "String" Signature the client SHOULD insert into<br />
new rich-text messages that will be sending from this identity.<br />
Clients MAY ignore this and/or combine this with a client-specific<br />
signature preference.<br />
<br />
o *htmlSignature*: "String" Signature the client SHOULD insert into<br />
new HTML messages that will be sending from this identity. This<br />
text MUST be an HTML snippet to be inserted into the<br />
"<body></body>" section of the new email. Clients MAY ignore this<br />
and/or combine this with a client-specific signature preference.<br />
<br />
o *mayDeleteIdentity*: "Boolean" Is the user allowed to delete this<br />
identity? Servers may wish to set this to false for the user's<br />
username or other default address.<br />
<br />
Multiple identities with the same email address MAY exist, to allow<br />
for different settings the user wants to pick between (for example<br />
with different names/signatures).<br />
<br />
6.1. getIdentities<br />
<br />
Identities can either be fetched explicitly by id, or all of them at<br />
once. To fetch identities, make a call to "getIdentities". It takes<br />
the following arguments:<br />
<br />
o *accountId*: "String|null" The Account to fetch the identities<br />
for. If "null", the primary account is used.<br />
<br />
o *ids*: "String[]|null" The ids of the identities to fetch. If<br />
"null", all identities in the account are be fetched.<br />
<br />
The response to _getIdentities_ is called _identities_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *state*: "String" A string encoding the current state on the<br />
server. This string will change if any identities change (that<br />
is, a new identity is created, a change is made to an existing<br />
identity, or an identity is deleted). It can be passed to<br />
_getIdentityUpdates_ to efficiently get the list of changes from<br />
the previous state.<br />
<br />
Jenkins Expires April 22, 2017 [Page 51]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *list*: "Identity[]" An array of the Identity objects requested.<br />
This will be the *empty array* if the _ids_ argument was the empty<br />
array, or contained only ids for identities that could not be<br />
found.<br />
<br />
o *notFound*: "String[]|null" This array contains the ids passed to<br />
the method for identities that do not exist, or "null" if all<br />
requested ids were found. It MUST be "null" if the _ids_ argument<br />
in the call was "null".<br />
<br />
The following errors may be returned instead of the _identities_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
6.2. getIdentityUpdates<br />
<br />
The _getIdentityUpdates_ call allows a client to efficiently update<br />
the state of its cached identities to match the new state on the<br />
server. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", the primary account will be used.<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the<br />
_identities_ response. The server will return the changes made<br />
since this state.<br />
<br />
o *maxChanges*: "Number|null" The maximum number of Identity ids to<br />
return in the response. The server MAY choose to clamp this value<br />
to a particular maximum or set a maximum if none is given by the<br />
client. If supplied by the client, the value MUST be a positive<br />
integer greater than 0. If a value outside of this range is<br />
given, the server MUST reject the call with an "invalidArguments"<br />
error.<br />
<br />
Jenkins Expires April 22, 2017 [Page 52]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *fetchRecords*: "Boolean|null" If "true", immediately after<br />
outputting an _identityUpdates_ response, an implicit call will be<br />
made to _getidentities_ with the _changed_ property of the<br />
response as the _ids_ argument, and the _fetchRecordProperties_<br />
argument as the _properties_ argument. If "false" or "null", no<br />
implicit call is made.<br />
<br />
The response to _getIdentityUpdates_ is called _identityUpdates_. It<br />
has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String" This is the _sinceState_ argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *hasMoreUpdates*: "Boolean" If "true", the client may call<br />
_getIdentityUpdates_ again with the _newState_ returned to get<br />
further updates. If "false", _newState_ is the current server<br />
state.<br />
<br />
o *changed*: "String[]" An array of Identity ids where a property of<br />
the identity has changed between the old state and the new state,<br />
or the identity has been created, and the identity has not been<br />
destroyed.<br />
<br />
o *removed*: "String[]" An array of Identity ids for identities<br />
which have been destroyed since the old state.<br />
<br />
If a _maxChanges_ is supplied, or set automatically by the server,<br />
the server must try to limit the number of ids across _changed_ and<br />
_removed_ to the number given. If there are more changes than this<br />
between the client's state and the current server state, the update<br />
returned MUST take the client to an intermediate state, from which<br />
the client can continue to call _getIdentityUpdates_ until it is<br />
fully up to date. The server MAY return more ids than the<br />
_maxChanges_ total if this is required for it to be able to produce<br />
an update to an intermediate state, but it SHOULD try to keep it<br />
close to the maximum requested.<br />
<br />
If an identity has been modified AND deleted since the oldState, the<br />
server should just return the id in the _removed_ array, but MAY<br />
return it in the _changed_ array as well. If an identity has been<br />
created AND deleted since the oldState, the server SHOULD remove the<br />
identity id from the response entirely, but MAY include it in the<br />
_removed_ array.<br />
<br />
Jenkins Expires April 22, 2017 [Page 53]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
The following errors may be returned instead of the "identityUpdates"<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A "description" property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old, or the server being unable to<br />
produce an update to an intermediate state when there are too many<br />
updates. The client MUST invalidate its Identity cache.<br />
<br />
6.3. setIdentities<br />
<br />
Modifying the state of Identity objects on the server is done via the<br />
_setIdentities_ method. This encompasses creating, updating and<br />
destroying Identity records.<br />
<br />
The _setIdentities_ method takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", the primary account will be used.<br />
<br />
o *ifInState*: "String|null" This is a state string as returned by<br />
the _getIdentities_ method. If supplied, the string must match<br />
the current state, otherwise the method MUST be aborted and a<br />
"stateMismatch" error returned. If "null", any changes will be<br />
applied to the current state.<br />
<br />
o *create*: "String[Identity]|null" A map of _creation id_ (an<br />
arbitrary string set by the client) to Identity objects<br />
(containing all properties except the id).<br />
<br />
o *update*: "String[Identity]|null" A map of id to Identity objects.<br />
The object may omit any property; only properties that have<br />
changed need be included.<br />
<br />
Jenkins Expires April 22, 2017 [Page 54]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *destroy*: "String[]|null" A list of ids for Identity objects to<br />
permanently delete.<br />
<br />
Each create, update or destroy is considered an atomic unit. It is<br />
permissible for the server to commit some of the changes but not<br />
others, however it is not permissible to only commit part of an<br />
update to a single identity.<br />
<br />
If a create, update or destroy is rejected, the appropriate error<br />
MUST be added to the notCreated/notUpdated/notDestroyed property of<br />
the response and the server MUST continue to the next create/update/<br />
destroy. It does not terminate the method.<br />
<br />
A *create* MAY be rejected with one of the following errors:<br />
<br />
o "maxQuotaReached": Returned if the user has reached a server-<br />
defined limit on the number of identities.<br />
<br />
o "emailNotPermitted": Returned if the user tries to create an<br />
identity with an email address the user does not allow them to<br />
send from.<br />
<br />
If the identity has "mayDeleteIdentity == false", any attempt to<br />
destroy it MUST be rejected with a "forbidden" error.<br />
<br />
If an id given cannot be found, the update or destroy MUST be<br />
rejected with a "notFound" set error.<br />
<br />
The response to _setIdentities_ is called _identitiesSet_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String|null" The state string that would have been<br />
returned by _getIdentities_ before making the requested changes,<br />
or "null" if the server doesn't know what the previous state<br />
string was.<br />
<br />
o *newState*: "String" The state string that will now be returned by<br />
_getIdentities_.<br />
<br />
o *created*: "String[Identity]" A map of the creation id to an<br />
object containing the *id* property for all successfully created<br />
identities.<br />
<br />
o *updated*: "String[]" A list of ids for identities that were<br />
successfully updated.<br />
<br />
Jenkins Expires April 22, 2017 [Page 55]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *destroyed*: "String[]" A list of ids for identities that were<br />
successfully destroyed.<br />
<br />
o *notCreated*: "String[SetError]" A map of creation id to a<br />
SetError object for each identity that failed to be created. The<br />
possible errors are defined in the description of the method for<br />
specific data types.<br />
<br />
o *notUpdated*: "String[SetError]" A map of Identity id to a<br />
SetError object for each identity that failed to be updated. The<br />
possible errors are defined in the description of the method for<br />
specific data types.<br />
<br />
o *notDestroyed*: "String[SetError]" A map of Identity id to a<br />
SetError object for each identity that failed to be destroyed.<br />
The possible errors are defined in the description of the method<br />
for specific data types.<br />
<br />
A *SetError* object has the following properties:<br />
<br />
o *type*: "String" The type of error.<br />
<br />
o *description*: "String|null" A description of the error to display<br />
to the user.<br />
<br />
The following errors may be returned instead of the _identitiesSet_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"accountReadOnly": Returned if the account has MailCapabilities with<br />
"isReadOnly == true".<br />
<br />
"requestTooLarge": Returned if the total number of objects to create,<br />
update or destroy exceeds the maximum number the server is willing to<br />
process in a single method call.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A _description_ property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
Jenkins Expires April 22, 2017 [Page 56]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
"stateMismatch": Returned if an _ifInState_ argument was supplied and<br />
it does not match the current state.<br />
<br />
7. SearchSnippets<br />
<br />
When doing a search on a "String" property, the client may wish to<br />
show the relevant section of the body that matches the search as a<br />
preview instead of the beginning of the message, and to highlight any<br />
matching terms in both this and the subject of the message. Search<br />
snippets represent this data.<br />
<br />
A *SearchSnippet* object has the following properties:<br />
<br />
o *messageId*: "String" The message id the snippet applies to.<br />
<br />
o *subject*: "String|null" If text from the filter matches the<br />
subject, this is the subject of the message HTML-escaped, with<br />
matching words/phrases wrapped in "<mark></mark>" tags. If it<br />
does not match, this is "null".<br />
<br />
o *preview*: "String|null" If text from the filter matches the<br />
plain-text or HTML body, this is the relevant section of the body<br />
(converted to plain text if originally HTML), HTML-escaped, with<br />
matching words/phrases wrapped in "<mark></mark>" tags, up to 256<br />
characters long. If it does not match, this is "null".<br />
<br />
It is server-defined what is a relevant section of the body for<br />
preview. If the server is unable to determine search snippets, it<br />
MUST return "null" for both the _subject_ and _preview_ properties.<br />
<br />
Note, unlike most data types, a SearchSnippet DOES NOT have a<br />
property called "id".<br />
<br />
7.1. getSearchSnippets<br />
<br />
To fetch search snippets, make a call to "getSearchSnippets". It<br />
takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the account to use for this<br />
call. If "null", defaults to the primary account.<br />
<br />
o *messageIds*: "String[]" The list of ids of messages to fetch the<br />
snippets for.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" The same filter as<br />
passed to getMessageList; see the description of this method for<br />
details.<br />
<br />
Jenkins Expires April 22, 2017 [Page 57]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
The response to "getSearchSnippets" is called "searchSnippets". It<br />
has the following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *filter*: "FilterCondition|FilterOperator|null" Echoed back from<br />
the call.<br />
<br />
o *list*: "SearchSnippet[]" An array of SearchSnippets objects for<br />
the requested message ids. This may not be in the same order as<br />
the ids that were in the request.<br />
<br />
o *notFound*: "String[]|null" An array of message ids requested<br />
which could not be found, or "null" if all ids were found.<br />
<br />
Since snippets are only based on immutable properties, there is no<br />
state string or update mechanism needed.<br />
<br />
The following errors may be returned instead of the _searchSnippets_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"requestTooLarge": Returned if the number of _messageIds_ requested<br />
by the client exceeds the maximum number the server is willing to<br />
process in a single method call.<br />
<br />
"cannotDoFilter": Returned if the server is unable to process the<br />
given _filter_ for any reason.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A "description" property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
8. Vacation Response<br />
<br />
The *VacationResponse* object represents the state of vacation-<br />
response related settings for an account. It has the following<br />
properties:<br />
<br />
Jenkins Expires April 22, 2017 [Page 58]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *id*: "String" The id of the object. This property is immutable.<br />
There is only ever one vacation response object, and its id is<br />
""singleton"".<br />
<br />
o *isEnabled* "Boolean" Should a vacation response be sent if a<br />
message arrives between the _fromDate_ and _toDate_?<br />
<br />
o *fromDate*: "Date|null" If _isEnabled_ is "true", the date/time<br />
after which messages that arrive should receive the user's<br />
vacation response, in UTC. If "null", the vacation response is<br />
effective immediately.<br />
<br />
o *toDate*: "Date|null" If _isEnabled_ is "true", the date/time<br />
after which messages that arrive should no longer receive the<br />
user's vacation response, in UTC. If "null", the vacation<br />
response is effective indefinitely.<br />
<br />
o *subject*: "String|null" The subject that will be used by the mail<br />
sent in response to messages when the vacation response is<br />
enabled. If null, an appropriate subject SHOULD be set by the<br />
server.<br />
<br />
o *textBody*: "String|null" The plain text part of the message to<br />
send in response to messages when the vacation response is<br />
enabled. If this is "null", when the vacation message is sent a<br />
plain-text body part SHOULD be generated from the _htmlBody_ but<br />
the server MAY choose to send the response as HTML only.<br />
<br />
o *htmlBody*: "String|null" The HTML message to send in response to<br />
messages when the vacation response is enabled. If this is<br />
"null", when the vacation message is sent an HTML body part MAY be<br />
generated from the _textBody_, or the server MAY choose to send<br />
the response as plain-text only.<br />
<br />
8.1. getVacationResponse<br />
<br />
There MUST only be exactly one VacationResponse object in an account.<br />
It MUST have the id ""singleton"".<br />
<br />
To fetch the vacation response object, make a call to<br />
"getVacationResponse". It takes the following argument:<br />
<br />
o *accountId*: "String|null" The Account to get the vacation<br />
response for. If "null", the primary account is used.<br />
<br />
The response to _getVacationResponse_ is called _vacationResponse_.<br />
It has the following arguments:<br />
<br />
Jenkins Expires April 22, 2017 [Page 59]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *list*: "VacationResponse[]" An array containing the single<br />
VacationResponse object.<br />
<br />
The following errors may be returned instead of the<br />
_vacationResponse_ response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
8.2. setVacationResponse<br />
<br />
Sets properties on the vacation response object. It takes the<br />
following arguments:<br />
<br />
o *accountId*: "String|null" The Account to set the vacation<br />
response for. If "null", the primary account is used.<br />
<br />
o *update*: "String[VacationResponse]|null" A map of id<br />
("singleton") to the VacationResponse object with new values for<br />
the properties you wish to change. The object may omit any<br />
property; only properties that have changed need be included.<br />
<br />
If any of the properties in the update are invalid (immutable and<br />
different to the current server value, wrong type), the server MUST<br />
reject the update with a SetError of type "invalidProperties". The<br />
SetError object SHOULD contain a property called _properties_ of type<br />
"String[]" that lists *all* the properties that were invalid. The<br />
object MAY also contain a _description_ property of type "String"<br />
with a user-friendly description of the problems.<br />
<br />
The response is called _vacationResponseSet_. It has the following<br />
arguments:<br />
<br />
o *updated*: "String[]" Contains the single id ("singleton") if the<br />
vacation response was successfully updated.<br />
<br />
o *notUpdated*: "String[SetError]" A map of id ("singleton") to a<br />
SetError object if the update failed.<br />
<br />
A *SetError* object has the following properties:<br />
<br />
o *type*: "String" The type of error.<br />
<br />
Jenkins Expires April 22, 2017 [Page 60]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
o *description*: "String|null" A description of the error to display<br />
to the user.<br />
<br />
The following errors may be returned instead of the<br />
_vacationResponseSet_ response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid (including using an id other than<br />
""singleton""). A "description" property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
9. References<br />
<br />
9.1. Normative References<br />
<br />
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate<br />
Requirement Levels", BCP 14, RFC 2119,<br />
DOI 10.17487/RFC2119, March 1997,<br />
<http://www.rfc-editor.org/info/rfc2119>.<br />
<br />
[RFC5322] Resnick, P., Ed., "Internet Message Format", RFC 5322,<br />
DOI 10.17487/RFC5322, October 2008,<br />
<http://www.rfc-editor.org/info/rfc5322>.<br />
<br />
[RFC7159] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data<br />
Interchange Format", RFC 7159, DOI 10.17487/RFC7159, March<br />
2014, <http://www.rfc-editor.org/info/rfc7159>.<br />
<br />
9.2. URIs<br />
<br />
[1] server.html<br />
<br />
[2] https://tools.ietf.org/html/rfc4648<br />
<br />
[3] http://tools.ietf.org/html/rfc2047<br />
<br />
Jenkins Expires April 22, 2017 [Page 61]<br />
Internet-Draft JMAP Mail October 2016<br />
<br />
Author's Address<br />
<br />
Neil Jenkins<br />
FastMail<br />
Level 1, 91 William St<br />
Melbourne VIC 3000<br />
Australia<br />
<br />
Email: neilj@fastmail.com<br />
URI: https://www.fastmail.com<br />
<br />
Jenkins Expires April 22, 2017 [Page 62]</div>
Sysop
http://djson.dj/index.php/Draft_Jenkins_JMAP
Draft Jenkins JMAP
2017-02-26T12:02:41Z
<p>Sysop: Created page with "<pre> JMAP N. Jenkins Internet-Draft FastMail Intended status: Standa..."</p>
<hr />
<div><pre><br />
JMAP N. Jenkins<br />
Internet-Draft FastMail<br />
Intended status: Standards Track October 19, 2016<br />
Expires: April 22, 2017<br />
<br />
JSON Meta Application Protocol<br />
draft-jenkins-jmap-00<br />
<br />
Abstract<br />
<br />
This document specifies a protocol for synchronising JSON-based data<br />
objects efficiently, with support for push and out-of-band binary<br />
data upload/download.<br />
<br />
Status of This Memo<br />
<br />
This Internet-Draft is submitted in full conformance with the<br />
provisions of BCP 78 and BCP 79.<br />
<br />
Internet-Drafts are working documents of the Internet Engineering<br />
Task Force (IETF). Note that other groups may also distribute<br />
working documents as Internet-Drafts. The list of current Internet-<br />
Drafts is at http://datatracker.ietf.org/drafts/current/.<br />
<br />
Internet-Drafts are draft documents valid for a maximum of six months<br />
and may be updated, replaced, or obsoleted by other documents at any<br />
time. It is inappropriate to use Internet-Drafts as reference<br />
material or to cite them other than as "work in progress."<br />
<br />
This Internet-Draft will expire on April 22, 2017.<br />
<br />
Copyright Notice<br />
<br />
Copyright (c) 2016 IETF Trust and the persons identified as the<br />
document authors. All rights reserved.<br />
<br />
This document is subject to BCP 78 and the IETF Trust's Legal<br />
Provisions Relating to IETF Documents<br />
(http://trustee.ietf.org/license-info) in effect on the date of<br />
publication of this document. Please review these documents<br />
carefully, as they describe your rights and restrictions with respect<br />
to this document. Code Components extracted from this document must<br />
include Simplified BSD License text as described in Section 4.e of<br />
the Trust Legal Provisions and are provided without warranty as<br />
described in the Simplified BSD License.<br />
<br />
Jenkins Expires April 22, 2017 [Page 1]<br />
Internet-Draft JMAP October 2016<br />
<br />
Table of Contents<br />
<br />
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3<br />
1.1. Notational Conventions . . . . . . . . . . . . . . . . . 4<br />
1.2. Terminology . . . . . . . . . . . . . . . . . . . . . . . 4<br />
1.2.1. User . . . . . . . . . . . . . . . . . . . . . . . . 4<br />
1.2.2. Accounts . . . . . . . . . . . . . . . . . . . . . . 5<br />
1.2.3. Data types and records . . . . . . . . . . . . . . . 5<br />
1.3. Ids . . . . . . . . . . . . . . . . . . . . . . . . . . . 5<br />
1.4. JSON as the data encoding format . . . . . . . . . . . . 5<br />
1.5. The JMAP API model . . . . . . . . . . . . . . . . . . . 6<br />
2. Authentication . . . . . . . . . . . . . . . . . . . . . . . 6<br />
2.1. Service autodiscovery . . . . . . . . . . . . . . . . . . 6<br />
2.2. Getting an access token . . . . . . . . . . . . . . . . . 7<br />
2.2.1. 200: Success, but more authorization required. . . . 8<br />
2.2.2. 201: Authentication is complete, access token<br />
created. . . . . . . . . . . . . . . . . . . . . . . 11<br />
2.2.3. 400: Malformed request . . . . . . . . . . . . . . . 13<br />
2.2.4. 403: Authentication step failed, but client may try<br />
again . . . . . . . . . . . . . . . . . . . . . . . . 13<br />
2.2.5. 404: Not found . . . . . . . . . . . . . . . . . . . 13<br />
2.2.6. 410: Restart authentication . . . . . . . . . . . . . 14<br />
2.2.7. 429: Rate limited . . . . . . . . . . . . . . . . . . 14<br />
2.2.8. 500: Internal server error . . . . . . . . . . . . . 14<br />
2.2.9. 503: Service unavailable . . . . . . . . . . . . . . 14<br />
2.3. Refetching URL endpoints . . . . . . . . . . . . . . . . 14<br />
2.3.1. 201: Authentication is complete, access token<br />
created. . . . . . . . . . . . . . . . . . . . . . . 14<br />
2.3.2. 403: Restart authentication . . . . . . . . . . . . . 15<br />
2.3.3. 404: Not found . . . . . . . . . . . . . . . . . . . 15<br />
2.3.4. 500: Internal server error . . . . . . . . . . . . . 15<br />
2.3.5. 503: Service unavailable . . . . . . . . . . . . . . 15<br />
2.4. Revoking an access token . . . . . . . . . . . . . . . . 15<br />
2.4.1. 204: Success . . . . . . . . . . . . . . . . . . . . 16<br />
2.4.2. 401: Unauthorized . . . . . . . . . . . . . . . . . . 16<br />
2.5. Authenticating HTTP requests . . . . . . . . . . . . . . 16<br />
2.5.1. Signed GET requests . . . . . . . . . . . . . . . . . 16<br />
3. Structured data exchange . . . . . . . . . . . . . . . . . . 18<br />
3.1. Making an API request . . . . . . . . . . . . . . . . . . 18<br />
3.1.1. 200: OK . . . . . . . . . . . . . . . . . . . . . . . 18<br />
3.1.2. 400: Bad Request . . . . . . . . . . . . . . . . . . 18<br />
3.1.3. 401: Unauthorized . . . . . . . . . . . . . . . . . . 18<br />
3.1.4. 404: Not Found . . . . . . . . . . . . . . . . . . . 19<br />
3.1.5. 413: Payload Too Large . . . . . . . . . . . . . . . 19<br />
3.1.6. 429: Rate limited . . . . . . . . . . . . . . . . . . 19<br />
3.1.7. 500: Internal Server Error . . . . . . . . . . . . . 19<br />
3.1.8. 503: Service Unavailable . . . . . . . . . . . . . . 19<br />
3.2. The structure of an API request . . . . . . . . . . . . . 19<br />
<br />
Jenkins Expires April 22, 2017 [Page 2]<br />
Internet-Draft JMAP October 2016<br />
<br />
3.3. Errors . . . . . . . . . . . . . . . . . . . . . . . . . 20<br />
3.4. Vendor-specific extensions . . . . . . . . . . . . . . . 21<br />
3.5. Security . . . . . . . . . . . . . . . . . . . . . . . . 21<br />
3.6. Concurrency . . . . . . . . . . . . . . . . . . . . . . . 21<br />
3.7. The Number datatype . . . . . . . . . . . . . . . . . . . 22<br />
3.8. The Date datatypes . . . . . . . . . . . . . . . . . . . 22<br />
3.9. Use of null . . . . . . . . . . . . . . . . . . . . . . . 22<br />
3.10. CRUD methods . . . . . . . . . . . . . . . . . . . . . . 22<br />
3.10.1. getFoos . . . . . . . . . . . . . . . . . . . . . . 23<br />
3.10.2. getFooUpdates . . . . . . . . . . . . . . . . . . . 24<br />
3.10.3. setFoos . . . . . . . . . . . . . . . . . . . . . . 26<br />
4. Downloading binary data . . . . . . . . . . . . . . . . . . . 29<br />
4.1. 200: OK . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />
4.2. 401: Unauthorized . . . . . . . . . . . . . . . . . . . . 30<br />
4.3. 404: Not Found . . . . . . . . . . . . . . . . . . . . . 30<br />
4.4. 503: Service Unavailable . . . . . . . . . . . . . . . . 30<br />
5. Uploading binary data . . . . . . . . . . . . . . . . . . . . 30<br />
5.1. 201: File uploaded successfully . . . . . . . . . . . . . 30<br />
5.2. 400: Bad request . . . . . . . . . . . . . . . . . . . . 31<br />
5.3. 401: Unauthorized . . . . . . . . . . . . . . . . . . . . 31<br />
5.4. 404: Not Found . . . . . . . . . . . . . . . . . . . . . 32<br />
5.5. 413: Request Entity Too Large . . . . . . . . . . . . . . 32<br />
5.6. 415: Unsupported Media Type . . . . . . . . . . . . . . . 32<br />
5.6.1. 429: Rate limited . . . . . . . . . . . . . . . . . . 32<br />
5.7. 503: Service Unavailable . . . . . . . . . . . . . . . . 32<br />
6. Push . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32<br />
6.1. Event Source . . . . . . . . . . . . . . . . . . . . . . 33<br />
6.2. Web hook . . . . . . . . . . . . . . . . . . . . . . . . 34<br />
6.2.1. setPushCallback . . . . . . . . . . . . . . . . . . . 34<br />
6.2.2. getPushCallback . . . . . . . . . . . . . . . . . . . 35<br />
7. References . . . . . . . . . . . . . . . . . . . . . . . . . 35<br />
7.1. Normative References . . . . . . . . . . . . . . . . . . 35<br />
7.2. URIs . . . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 36<br />
<br />
1. Introduction<br />
<br />
JMAP is a generic protocol for synchronising data, such as mail,<br />
calendars or contacts, between a client and a server. It is<br />
optimised for mobile and web environments, and aims to provide a<br />
consistent interface to different data types.<br />
<br />
This specification is for the generic mechanism of authentication and<br />
synchronisation. Further specifications define the data models for<br />
different data types that may be synchronised via JMAP.<br />
<br />
JMAP is designed to make efficient use of limited network resources.<br />
Multiple API calls may be batched in a single request to the server,<br />
<br />
Jenkins Expires April 22, 2017 [Page 3]<br />
Internet-Draft JMAP October 2016<br />
<br />
reducing round trips and improving battery life on mobile devices.<br />
Push connections remove the need for polling, and an efficient delta<br />
update mechanism ensures a minimum of data is transferred.<br />
<br />
JMAP is designed to be horizontally scalable to a very large number<br />
of users. This is facilitated by the separate end points for users<br />
after login, the separation of binary and structured data, and a<br />
shared data model that does not allow data dependencies between<br />
accounts.<br />
<br />
1.1. Notational Conventions<br />
<br />
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",<br />
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this<br />
document are to be interpreted as described in [RFC2119].<br />
<br />
The underlying format used for this specification is JSON.<br />
Consequently, the terms "object" and "array" as well as the four<br />
primitive types (strings, numbers, booleans, and null) are to be<br />
interpreted as described in Section 1 of [RFC7159].<br />
<br />
Some examples in this document contain "partial" JSON documents used<br />
for illustrative purposes. In these examples, three periods "..."<br />
are used to indicate a portion of the document that has been removed<br />
for compactness.<br />
<br />
Types signatures are given for all JSON objects in this document.<br />
The following conventions are used:<br />
<br />
o "Boolean|String" - The value is either a JSON "Boolean" value, or<br />
a JSON "String" value.<br />
<br />
o "Foo" - Any name that is not a native JSON type means an object<br />
for which the properties (and their types) are defined elsewhere<br />
within this document.<br />
<br />
o "Foo[]" - An array of objects of type "Foo".<br />
<br />
o "String[Foo]" - A JSON "Object" being used as a map (associative<br />
array), where all the values are of type "Foo".<br />
<br />
1.2. Terminology<br />
<br />
1.2.1. User<br />
<br />
A user represents a set of permissions relating to what data can be<br />
seen. To access data in JMAP, you first authenticate as a particular<br />
user.<br />
<br />
Jenkins Expires April 22, 2017 [Page 4]<br />
Internet-Draft JMAP October 2016<br />
<br />
1.2.2. Accounts<br />
<br />
An account is a collection of data.<br />
<br />
All data, other than the Account objects themselves, belong to a<br />
single account. A single account may contain an arbitrary set of<br />
data, for example a collection of mail, contacts and calendars. Most<br />
operations in JMAP are isolated to a single account; there are a few<br />
explicit operations to copy data between them. Certain properties<br />
are guaranteed for data within the same account, for example<br />
uniqueness of ids within a type in that account.<br />
<br />
An account is not the same as a user, although it is common for the<br />
primary account to directly belong to the user. For example, you may<br />
have an account that contains data for a group or business, to which<br />
multiple users have access. Users may also have access to accounts<br />
belonging to another user if that user is sharing some of their data.<br />
<br />
1.2.3. Data types and records<br />
<br />
JMAP provides a uniform interface for creating, retrieving, updating<br />
and deleting various types of objects. A *data type* is a collection<br />
of named, typed properties, just like the schema for a database<br />
table. Each instance of a data type is called a *record*.<br />
<br />
1.3. Ids<br />
<br />
All object ids are assigned by the server, and are immutable. They<br />
MUST be unique among all objects of the *same type* within the *same<br />
account*. Ids may clash across accounts, or for two objects of<br />
different types within the same account.<br />
<br />
Ids are always "String"s. An id MUST be a valid UTF-8 string of at<br />
least 1 character in length and maximum 256 bytes in size, but MUST<br />
NOT start with the "#" character, as this is reserved for doing back<br />
references during object creation (see the _setFoos_ description).<br />
<br />
1.4. JSON as the data encoding format<br />
<br />
JSON is a text-based data interchange format as specified in<br />
[RFC7159]. The I-JSON format defined in [RFC7493] is a strict subset<br />
of this, adding restrictions to avoid potentially confusing scenarios<br />
(for example, it mandates that an object MUST NOT have two properties<br />
with the same key).<br />
<br />
All data sent from the client to the server or from the server to the<br />
client (except binary file upload/download) MUST be valid I-JSON<br />
<br />
Jenkins Expires April 22, 2017 [Page 5]<br />
Internet-Draft JMAP October 2016<br />
<br />
according to the RFC, and is therefore case-sensitive and encoded in<br />
UTF-8.<br />
<br />
1.5. The JMAP API model<br />
<br />
All data exchanges are authenticated using an access token.<br />
Authentication is covered in section 2.<br />
<br />
An authenticated client may exchange data with the server using four<br />
different mechanisms:<br />
<br />
1. The client may make an API request to the server to get or set<br />
structured data. This request consists of an ordered series of<br />
method calls. These are processed by the server, which then<br />
returns an ordered series of responses. This is described in<br />
section 3.<br />
<br />
2. The client may download binary files from the server. This is<br />
detailed in section 4.<br />
<br />
3. The client may upload binary files to the server. This is<br />
specified in section 5.<br />
<br />
4. The client may connect to a push channel on the server, to be<br />
notified when data has changed. This is explained in section 6.<br />
<br />
2. Authentication<br />
<br />
When connecting to any JMAP server, the client must first gain an<br />
access token. It cannot just use a username/password directly. This<br />
allows the server to know (and show the user) which clients currently<br />
have access to the account, and to be able to revoke access<br />
individually.<br />
<br />
The server may support multiple different mechanisms for<br />
authenticating a user to gain the access token. It is expected that<br />
further types may be added in future extensions to the JMAP<br />
specification.<br />
<br />
2.1. Service autodiscovery<br />
<br />
To begin authentication, the client needs to know the authentication<br />
URL for the JMAP server.<br />
<br />
There are two standardised autodiscovery methods in use for internet<br />
protocols:<br />
<br />
o *DNS srv* See [RFC6186] and [RFC6764]<br />
<br />
Jenkins Expires April 22, 2017 [Page 6]<br />
Internet-Draft JMAP October 2016<br />
<br />
o *.well-known/servicename* See [RFC5785]<br />
<br />
A JMAP-supporting host for the domain "example.com" SHOULD publish a<br />
SRV record "_jmaps._tcp.example.com" which gives a _hostname_ and<br />
_port_ (usually port "443"). The authentication URL is then<br />
"https://${hostname}[:${port}]/.well-known/jmap" (following any<br />
redirects).<br />
<br />
If the client has a username in the form of an email address, it MAY<br />
use the domain portion of this to attempt autodiscovery of the JMAP<br />
server.<br />
<br />
To support clients that are unable to do SRV lookups, the server<br />
SHOULD make the _hostname_ the same domain as the username if<br />
possible.<br />
<br />
2.2. Getting an access token<br />
<br />
Authorization always starts with the client making a POST request to<br />
the authentication URL (found either via service autodiscovery or<br />
manual entry). The request MUST be of type "application/json" and<br />
specify an "Accept: application/json" header. The body of the<br />
request MUST be a single JSON object, encoded in UTF-8, with the<br />
following properties:<br />
<br />
o *username*: "String" The username the client wishes to<br />
authenticate. This is normally the primary email address of the<br />
user.<br />
<br />
o *clientName*: "String" The name of the client software. e.g.<br />
"Mozilla Thunderbird".<br />
<br />
o *clientVersion*: "String" Information to identify the version of<br />
the client. This MUST change for any changed client code (e.g. a<br />
version control tag or counter for development software) and<br />
SHOULD sort lexically later for newer versions.<br />
<br />
o *deviceName*: "String" A human-friendly string to identify the<br />
device making the request, e.g. "Joe Blogg's iPhone".<br />
<br />
The server may use the client/device information to help identify the<br />
login to the user in a login log or other security reporting.<br />
Although hopefully unnecessary, they may also be helpful for working<br />
around client bugs in the future.<br />
<br />
The server will respond with one of the following HTTP status codes:<br />
<br />
Jenkins Expires April 22, 2017 [Page 7]<br />
Internet-Draft JMAP October 2016<br />
<br />
2.2.1. 200: Success, but more authorization required.<br />
<br />
The response body will be a single JSON object with the following<br />
properties.<br />
<br />
o *loginId*: "String" An id from the server to allow it to connect<br />
the next request with previous requests in the login process.<br />
This SHOULD be of limited time validity (e.g. 15 minutes from<br />
previous call).<br />
<br />
o *methods*: "AuthMethod[]" A list of the supported authentication<br />
methods to continue with authentication. See below for definition<br />
of the *AuthMethod* object.<br />
<br />
o *prompt*: "String|null" A message to display in the client to the<br />
user. The client MUST treat this as plain text, but SHOULD<br />
automatically hyperlink any URLs it finds if a system browser is<br />
available.<br />
<br />
This is the standard response to an initial request. Note, a server<br />
may return this even if the username is not actually active, to<br />
prevent enumeration. The client should then pick one of the<br />
_methods_ from the list in the response to continue with<br />
authentication (if no methods supported by the client are in the<br />
list, it will not be able to get an access token).<br />
<br />
An *AuthMethod* object MUST have a *type* property. This is a<br />
"String" representing the method of authentication. For some types,<br />
there may be other values required on the AuthMethod object in<br />
addition; see the description of types below. The following types<br />
are currently defined, but more may be added in the future. A client<br />
SHOULD offer the user the option to choose any of the method types<br />
returned that the client supports. The client MUST ignore any types<br />
it does not understand:<br />
<br />
o "external": The user must do something out-of-band to authorize<br />
the app. The server SHOULD use the _prompt_ property of the<br />
response to tell the user what they need to do. A client that<br />
supports the _external_ authorisation type MUST offer a mechanism<br />
for the user to indicate to the client when they have completed<br />
the out-of-band authentication.<br />
<br />
o "oauth": OAuth based authentication. For OAuth integration, see<br />
the docs of the service in question, since every service<br />
implements it slightly differently and the client must register<br />
with the service beforehand to use it. If using this method, an<br />
access token is obtained entirely through the OAuth mechanism from<br />
this point on, and requests will be authenticated as per the OAuth<br />
<br />
Jenkins Expires April 22, 2017 [Page 8]<br />
Internet-Draft JMAP October 2016<br />
<br />
spec. See the "Refetching URL endpoints" section below for how to<br />
obtain the URL endpoints after successfully authenticating using<br />
OAuth.<br />
<br />
o "password": The user must input their current password for the<br />
account.<br />
<br />
o "totp": The user must input a TOTP [1] code from a device<br />
registered with the account.<br />
<br />
o "yubikeyotp": The user must input a Yubico OTP [2] code from a<br />
Yubikey registered with the account.<br />
<br />
o "u2f": The user must sign a challenge using a FIDO U2F [3] key<br />
registered with the account. The AuthMethod object for this type<br />
MUST also have the following properties:<br />
<br />
* *appId*: "String" The app id to use.<br />
<br />
* *signChallenge*: "String" The challenge to be signed by the U2F<br />
key.<br />
<br />
* *registeredKeys*: "RegisteredKey[]" The keys associated with<br />
this user. A *RegisteredKey* object has the following<br />
properties:<br />
<br />
* *version*: "String" The U2F protocol version.<br />
<br />
* *keyHandle*: "String" The key handle of the registered key.<br />
<br />
o "sms": The user must input a one-time code sent via SMS to a phone<br />
number registered with the account. The AuthMethod object for<br />
this type MUST also have the following property:<br />
<br />
* *phoneNumbers*: "LoginPhone[]|null" An array of *LoginPhone*<br />
objects, each of which represents a phone registered with the<br />
account. A *LoginPhone* object has the following properties:<br />
<br />
+ *id*: "String" The id of the phone. This is used when<br />
asking the server to send a code.<br />
<br />
+ *number*: "String" The phone number to display to the user<br />
to remind them which number the SMS will be/was sent to.<br />
This MAY have some characters replaced with an "X" or other<br />
"blanked-out" character if the server does not wish to<br />
disclose the full phone number at this point. e.g. if the<br />
phone registered with the account is "+61 123 456 789", the<br />
server might return "+61 1XX XXX X89" as the number.<br />
<br />
Jenkins Expires April 22, 2017 [Page 9]<br />
Internet-Draft JMAP October 2016<br />
<br />
+ *isCodeSent*: "Boolean" Has the verification code been sent<br />
to this number yet? The server MAY choose to send the SMS<br />
before the first time this auth option is returned, or may<br />
wait for the user to explicitly request it.<br />
<br />
If not using ""oauth"", the user will at some point indicate to the<br />
client to continue authentication (after inputing any required<br />
token/code/password dependent on the auth method chosed). At this<br />
point the client submits a POST request to the same URL as before,<br />
with the body being a single JSON object with the following<br />
properties:<br />
<br />
o *loginId*: "String" The _loginId_ the server sent from the<br />
previous request.<br />
<br />
o *type*: "String" The type of the method chosen to continue<br />
authentication.<br />
<br />
o *value*: "*" The value as appropriate for the given type:<br />
<br />
* "external": "null"<br />
<br />
* "password"/"totp"/"yubikeyotp"/"sms": "String" - the password/<br />
one-time code.<br />
<br />
* "u2f": "SignResponse" - an object with _keyHandle_,<br />
_signatureData_ and _clientData_ "String" properties, as<br />
defined in the U2F spec.<br />
<br />
Note: The client SHOULD NOT store any password/code the user has<br />
entered beyond what is required to submit it to the server in this<br />
step.<br />
<br />
The server will then return one of the same set of responses as<br />
before, which should be handled the same (for example, if two-factor<br />
authentication is required, a "200" response may be returned again<br />
and TOTP/U2F prompted for).<br />
<br />
If the user chooses to authenticate using SMS, they may need to<br />
request the server to send the code to a particular number before<br />
they can submit a code. To do this, the client submits a POST<br />
request to the same URL as before, with the body being a single JSON<br />
object with the following properties:<br />
<br />
o *loginId*: "String" The _loginId_ the server sent from the<br />
previous request.<br />
<br />
Jenkins Expires April 22, 2017 [Page 10]<br />
Internet-Draft JMAP October 2016<br />
<br />
o *sendCodeTo*: "String" The id of the phone number to send the code<br />
to.<br />
<br />
The server SHOULD send the code to the given phone if the phone id is<br />
valid. If the code has already been sent, it is server-dependent<br />
whether it is sent again or ignored. The server MUST return one of<br />
the same set of responses as before, which should be handled the same<br />
(in most cases this will be a "200" response identical to before<br />
except that the _isCodeSent_ property for the phone will now be<br />
"true").<br />
<br />
2.2.2. 201: Authentication is complete, access token created.<br />
<br />
The response body will be a single JSON object with the following<br />
properties.<br />
<br />
o *username*: "String" The username that was successfully<br />
authenticated.<br />
<br />
o *accessToken*: "String" The secret token to be used by the client<br />
to authenticate all future JMAP requests. The client should keep<br />
this secure, preferably in an OS keychain or the like. Since<br />
tokens should not be reused across devices or clients, the client<br />
SHOULD NOT reveal this token to the user.<br />
<br />
o *accounts*: "String[Account]" A map of *account id* to Account<br />
object for each account the user has access to. A single access<br />
token may provide access to multiple accounts, for example if<br />
another user is sharing their mail with the logged in user, or if<br />
there is an account that contains data for a group or business.<br />
All data belongs to a single account. With the exception of a few<br />
explicit operations to copy data between accounts, all JMAP<br />
methods take an _accountId_ argument that specifies on which<br />
account the operations are to take place. This argument is always<br />
optional; if not specified, the primary account is used. All ids<br />
(other than Account ids of course) are only unique within their<br />
account. In the event of a severe internal error, a server may<br />
have to reallocate ids or do something else that violates standard<br />
JMAP data constraints. In this situation, the data on the server<br />
is no longer compatible with cached data the client may have from<br />
before. The server MUST treat this as though the account has been<br />
deleted and then recreated with a new account id. Clients will<br />
then be forced to throw away any data with the old account id and<br />
refetch all data from scratch. An *Account* object has the<br />
following properties:<br />
<br />
Jenkins Expires April 22, 2017 [Page 11]<br />
Internet-Draft JMAP October 2016<br />
<br />
* *name*: "String" A user-friendly string to show when presenting<br />
content from this account, e.g. the email address representing<br />
the owner of the account.<br />
<br />
* *isPrimary*: "Boolean" This MUST be true for exactly one of the<br />
accounts returned. This is to be considered the user's main or<br />
default account by the client.<br />
<br />
* *isReadOnly*: "Boolean" This is "true" if the entire account is<br />
read-only.<br />
<br />
* *hasDataFor*: "String[]" A list of the data profiles available<br />
in this account. Each future JMAP data types specification<br />
will define a profile name to encompass that set of types.<br />
<br />
o *capabilities*: "String[Object]" An object specifying the<br />
capabilities of this server. The keys are URIs, which specify the<br />
specifications supported by the server. The value for each of<br />
these keys is an object that MAY include further information about<br />
the server's capabilities in relation to that spec. The client<br />
MUST ignore any properties it does not understand. The<br />
capabilities object MUST include a property called "{TODO: URI for<br />
this spec}". The value of this property is an object which SHOULD<br />
contain the following information on server capabilities:<br />
<br />
* *maxSizeUpload*: "Number" The maximum file size, in bytes, that<br />
the server will accept for a single file upload (for any<br />
purpose).<br />
<br />
* *maxConcurrentUpload*: "Number" The maximum number of<br />
concurrent requests the server will accept to the upload<br />
endpoint.<br />
<br />
* *maxSizeRequest*: "Number" The maximum size, in bytes, that the<br />
server will accept for a single request to the API endpoint.<br />
<br />
* *maxConcurrentRequests*: "Number" The maximum number of<br />
concurrent requests the server will accept to the API endpoint.<br />
<br />
* *maxCallsInRequest*: "Number" The maximum number of method<br />
calls the server will accept in a single request to the API<br />
endpoint.<br />
<br />
* *maxObjectsInGet*: "Number" The maximum number of obje ts that<br />
the client may request in a single "getFoos" type method call.<br />
<br />
Jenkins Expires April 22, 2017 [Page 12]<br />
Internet-Draft JMAP October 2016<br />
<br />
* *maxObjectsInSet*: "Number" The maximum number of objects the<br />
client may send to create, update or destroy in a single<br />
"setFoos" type method call.<br />
<br />
Future specifications will define their own properties on the<br />
capabilities object.<br />
<br />
o *apiUrl*: "String" The URL to use for JMAP API requests.<br />
<br />
o *downloadUrl*: "String" The URL endpoint to use when downloading<br />
files (see the Download section of this spec), in [RFC6570] URI<br />
Template (level 1) format. The URL MUST contain a variable called<br />
"blobId". The URL SHOULD contain a variable called "name".<br />
<br />
o *uploadUrl*: "String" The URL endpoint to use when uploading files<br />
(see the Upload section of this spec).<br />
<br />
o *eventSourceUrl*: "String" The URL to connect to for push events<br />
(see the Push section of this spec).<br />
<br />
URLs are returned only after logging in. This allows different URLs<br />
to be used for users located in different geographic datacentres<br />
within the same service.<br />
<br />
Note, if authentication is done via IP or mobile subscriber ID or<br />
some similar mechanism, a "201" response MAY be returned in response<br />
to the initial request (with just the username and client info).<br />
<br />
2.2.3. 400: Malformed request<br />
<br />
The request is of the wrong content type, or does not contain data in<br />
the expected format. The client MUST NOT retry the same request.<br />
There is no content in the response.<br />
<br />
2.2.4. 403: Authentication step failed, but client may try again<br />
<br />
Returned in response to a continuation request which failed (e.g. the<br />
password entered was not correct, or the out-of-band step was not<br />
completed successfully). The response body will be a single JSON<br />
object with the same properties as the "200" response, and the client<br />
may try again.<br />
<br />
2.2.5. 404: Not found<br />
<br />
The JMAP authentication server is not available at this address. The<br />
client needs to rediscover the authentication URL. There is no<br />
content in the response.<br />
<br />
Jenkins Expires April 22, 2017 [Page 13]<br />
Internet-Draft JMAP October 2016<br />
<br />
2.2.6. 410: Restart authentication<br />
<br />
The login attempt has failed permanently. This may be due to a<br />
password being incorrect, the login id expiring, or any other reason.<br />
The client MUST restart authentication (go back to sending the<br />
username and client info to the server). There is no content in the<br />
response.<br />
<br />
2.2.7. 429: Rate limited<br />
<br />
Returned if the server is temporarily blocking this IP/client from<br />
authenticating. This may be due to too many failed password<br />
attempts, or detected username enumeration attempts, or any other<br />
reason. (Legitimate) clients should wait a while then try again.<br />
There is no content in the response.<br />
<br />
2.2.8. 500: Internal server error<br />
<br />
Something has gone wrong internally, and the server is in a broken<br />
state. Don't automatically retry. There is no content in the<br />
response.<br />
<br />
2.2.9. 503: Service unavailable<br />
<br />
The server is currently down. Try again later with exponential<br />
backoff. There is no content in the response.<br />
<br />
2.3. Refetching URL endpoints<br />
<br />
A server MAY (although SHOULD NOT) move end points for any services<br />
other than authentication at any time. If a request to the API/file<br />
upload/event source endpoint returns a "404", the client MUST refetch<br />
the URL endpoints. To do this, it should make an authenticated GET<br />
request to the authentication URL (see below for how to authenticate<br />
requests).<br />
<br />
For OAuth logins, this is how the URLs may be fetched initially as<br />
well.<br />
<br />
The server MUST respond with one of the following status codes:<br />
<br />
2.3.1. 201: Authentication is complete, access token created.<br />
<br />
The request was successful. The response will be of type<br />
"application/json" and consists of a single JSON object containing<br />
the following properties:<br />
<br />
Jenkins Expires April 22, 2017 [Page 14]<br />
Internet-Draft JMAP October 2016<br />
<br />
o *username*: "String" The username that was successfully<br />
authenticated.<br />
<br />
o *accounts*: "String[Account]" An object representing the accounts<br />
the user has access to. See the full description above.<br />
<br />
o *capabilities*: "String[Object]" An object specifying the<br />
capabilities of this server. See the full description above.<br />
<br />
o *apiUrl*: "String" The URL to use for JMAP API requests.<br />
<br />
o *downloadUrl*: "String" The URL endpoint to use when downloading<br />
files (see above).<br />
<br />
o *uploadUrl*: "String" The URL endpoint to use when uploading files<br />
(see the Upload section of this spec).<br />
<br />
o *eventSourceUrl*: "String" The URL to connect to for push events<br />
(see the Push section of this spec).<br />
<br />
2.3.2. 403: Restart authentication<br />
<br />
The "Authorization" header was missing or did not contain a valid<br />
token. Reauthenticate and then retry the request. There is no<br />
content in the response.<br />
<br />
2.3.3. 404: Not found<br />
<br />
The JMAP server is no longer here. There is no content in the<br />
response.<br />
<br />
2.3.4. 500: Internal server error<br />
<br />
Something has gone wrong internally, and the server is in a broken<br />
state. Don't automatically retry. There is no content in the<br />
response.<br />
<br />
2.3.5. 503: Service unavailable<br />
<br />
The server is currently down. Try again later with exponential<br />
backoff. There is no content in the response.<br />
<br />
2.4. Revoking an access token<br />
<br />
The validity of an access token is determined by the server. It may<br />
be valid for a limited time only, or expire after a certain time of<br />
inactivity, or be valid indefinitely etc. If an access token<br />
<br />
Jenkins Expires April 22, 2017 [Page 15]<br />
Internet-Draft JMAP October 2016<br />
<br />
expires, it MUST NOT be resurrected. The client MUST restart the<br />
authentication process to get a new access token.<br />
<br />
For OAuth, see the provider's documentation on revoking access<br />
tokens.<br />
<br />
Otherwise, a client may revoke an access token at any time by making<br />
an authenticated DELETE HTTP request to the authentication URL (the<br />
one used to get the token in the first place). The response from the<br />
server will be one of the following:<br />
<br />
2.4.1. 204: Success<br />
<br />
The access token has now been revoked. There is no content in the<br />
response.<br />
<br />
2.4.2. 401: Unauthorized<br />
<br />
Failed due to missing "Authorization" header, or the "Authorization"<br />
header did not contain a valid access token. As per the HTTP spec,<br />
the response MUST have a "WWW-Authenticate: Bearer" header. There is<br />
no content in the response.<br />
<br />
2.5. Authenticating HTTP requests<br />
<br />
All HTTP requests other than to the authentication URL must be<br />
authenticated. To do this, the client MUST add an "Authorization"<br />
header to each request.<br />
<br />
Once authenticated, the client will have an access token. This is<br />
used with the "Bearer" scheme as specified in [RFC6750] to<br />
authenticate HTTP requests.<br />
<br />
For example, if _user@example.com_ successfully logged in and the<br />
client received an _accessToken_ of "abcdef1234567890", to<br />
authenticate requests you would add the following header:<br />
<br />
Authorization: Bearer abcdef1234567890<br />
<br />
2.5.1. Signed GET requests<br />
<br />
Sometimes, particularly in the browser context, authenticating a GET<br />
request using the usual "Authorization" header is not easily<br />
implemented. In such situations, a client may use a signed request<br />
instead. A signed request is an unauthenticated GET request<br />
containing a special query string parameter (a so-called "token").<br />
The process below describes how a client can obtain such a token,<br />
then use it to download the file:<br />
<br />
Jenkins Expires April 22, 2017 [Page 16]<br />
Internet-Draft JMAP October 2016<br />
<br />
1. The client makes an authenticated POST request to the URL for<br />
which it is unable to make an authenticated GET request. If<br />
authorization is granted, the server MUST send the token back as<br />
the body of the response, in one of the following forms:<br />
<br />
* A JSON Web Token [4], in this case the response MUST have a<br />
"Content-Type" of "application/jwt".<br />
<br />
* A plain "String" or any other data structure, in this case the<br />
response SHOULD have a "Content-Type" of "text/plain" unless<br />
another standardised MIME type is applicable.<br />
<br />
2. The client makes an unauthenticated GET request to the same URL<br />
but with "access_token=<response to the previous request>"<br />
appended to the query part (see [RFC3986] section 3.4 for<br />
details). If no query part was present in the URL before this<br />
operation, a new query part is created. The server MUST send<br />
back the contents of the requested file, as if it were requested<br />
through a standard authenticated GET request to the URL.<br />
<br />
The server SHOULD expire any given signing "token" quickly for<br />
obvious security reasons, but the actual expiration policy is up to<br />
the server implementation.<br />
<br />
Sample HTTP exchange demonstrating the use of a signed request to<br />
download a file:<br />
<br />
o Obtain a token<br />
<br />
POST /jmap/download/act1/blob2/mydocument.pdf HTTP/1.1<br />
Host: server.example.com<br />
Authorization: Bearer abcdef1234567890<br />
<br />
HTTP/1.1 200 OK<br />
Content-Type: text/plain<br />
Content-Length: 10<br />
<br />
abcDEfGhIJ<br />
<br />
o Download the file<br />
<br />
Jenkins Expires April 22, 2017 [Page 17]<br />
Internet-Draft JMAP October 2016<br />
<br />
GET /jmap/download/act1/blob2/mydocument.pdf?access_token=abcDEfGhIJ HTTP/1.1<br />
Host: server.example.com<br />
<br />
HTTP/1.1 200 OK<br />
Content-Type: application/pdf<br />
Content-Length: 987654<br />
<br />
<< binary content >><br />
<br />
3. Structured data exchange<br />
<br />
The client may make an API request to the server to get or set<br />
structured data. This request consists of an ordered series of<br />
method calls. These are processed by the server, which then returns<br />
an ordered series of responses.<br />
<br />
3.1. Making an API request<br />
<br />
To make an API request, the client makes an authenticated POST<br />
request to the API URL; see the Authentication section of the spec<br />
for how to discover this URL and how to authenticate requests.<br />
<br />
The request MUST have a content type of "application/json" and be<br />
encoded in UTF-8.<br />
<br />
The server SHOULD respond with one of the following HTTP response<br />
codes:<br />
<br />
3.1.1. 200: OK<br />
<br />
The API request was successful. The response will be of type<br />
"application/json" and consists of the response to the API calls, as<br />
described below.<br />
<br />
3.1.2. 400: Bad Request<br />
<br />
The request was malformed. For example, it may have had the wrong<br />
content type, or have had a JSON object that did not conform to the<br />
API calling structure (see _The structure of an API request_ below).<br />
The client SHOULD NOT retry the same request. There is no content in<br />
the response.<br />
<br />
3.1.3. 401: Unauthorized<br />
<br />
The "Authorization" header was missing or did not contain a valid<br />
token. Reauthenticate and then retry the request. As per the HTTP<br />
spec, the response MUST have a "WWW-Authenticate" header listing the<br />
<br />
Jenkins Expires April 22, 2017 [Page 18]<br />
Internet-Draft JMAP October 2016<br />
<br />
available authentication schemes. There is no content in the<br />
response.<br />
<br />
3.1.4. 404: Not Found<br />
<br />
The API endpoint has moved. See the Authentication section of the<br />
spec for how to rediscover the current URL to use. There is no<br />
content in the response.<br />
<br />
3.1.5. 413: Payload Too Large<br />
<br />
Returned if the client makes a request with more method calls than<br />
the server is willing to accept in a single request, or if the total<br />
bytes of the request is larger than the max size the server is<br />
willing to accept.<br />
<br />
3.1.6. 429: Rate limited<br />
<br />
Returned if the client has made too many requests recently, or has<br />
too many concurrent requests currently in progress. Clients SHOULD<br />
wait a while then try again. The response MAY include a "Retry-<br />
After" header indicating how long to wait before making a new<br />
request.<br />
<br />
3.1.7. 500: Internal Server Error<br />
<br />
Something has gone wrong internally, and the server is in a broken<br />
state. Don't automatically retry. There is no content in the<br />
response.<br />
<br />
3.1.8. 503: Service Unavailable<br />
<br />
The server is currently down. Try again later with exponential<br />
backoff. There is no content in the response.<br />
<br />
3.2. The structure of an API request<br />
<br />
The client initiates an API request by sending the server a JSON<br />
array. Each element in this array is another array representing a<br />
method invocation on the server. The server will process the method<br />
calls and return a response consisting of an array in the same<br />
format. Each method call always contains three elements:<br />
<br />
1. The *name* of the method to call, or the name of the response<br />
from the server. This is a "String".<br />
<br />
2. An "Object" containing _named_ *arguments* for that method or<br />
response.<br />
<br />
Jenkins Expires April 22, 2017 [Page 19]<br />
Internet-Draft JMAP October 2016<br />
<br />
3. A *client id*: an arbitrary "String" to be echoed back with the<br />
responses emitted by that method call (as we'll see lower down, a<br />
method may return 1 or more responses, as some methods make<br />
implicit calls to other ones).<br />
<br />
Example query:<br />
<br />
[<br />
["method1", {"arg1": "arg1data", "arg2": "arg2data"}, "#1"],<br />
["method2", {"arg1": "arg1data"}, "#2"],<br />
["method3", {}, "#3"]<br />
]<br />
<br />
The method calls MUST be processed sequentially, in order. Each API<br />
request (which, as shown, may contain multiple method calls) receives<br />
a JSON response in exactly the same format. The output of the<br />
methods MUST be added to the array in the same order as the methods<br />
are processed.<br />
<br />
Example response:<br />
<br />
[<br />
["responseFromMethod1", {"arg1": 3, "arg2": "foo"}, "#1"],<br />
["responseFromMethod2", {"isBlah": true}, "#2"],<br />
["anotherResponseFromMethod2", {<br />
"data": 10,<br />
"yetmoredata": "Hello"<br />
}, "#2"],<br />
["aResponseFromMethod3", {}, "#3"]<br />
]<br />
<br />
3.3. Errors<br />
<br />
If the data sent as an API request is not valid JSON or does not<br />
match the structure above, an error will be returned at the transport<br />
level. For example, when using JMAP over HTTP, a "400 Bad Request"<br />
error will be returned at the HTTP level.<br />
<br />
Possible errors for each method are specified in the method<br />
descriptions. If a method encounters an error, the appropriate<br />
"error" response MUST be inserted at the current point in the output<br />
array and, unless otherwise specified, further processing MUST NOT<br />
happen within that method call.<br />
<br />
Any further method calls in the request MUST then be processed as<br />
normal.<br />
<br />
An "error" response looks like this:<br />
<br />
Jenkins Expires April 22, 2017 [Page 20]<br />
Internet-Draft JMAP October 2016<br />
<br />
["error", {<br />
type: "unknownMethod"<br />
}, "client-id"]<br />
<br />
The response name is "error", and it has a type property as specified<br />
in the method description. Other properties may be present with<br />
further information; these are detailed in the method descriptions<br />
where appropriate.<br />
<br />
Any method MAY return an error of type "serverError" if an unexpected<br />
or unknown error occurs during the processing of that call. The<br />
state of the server after such an error is undefined.<br />
<br />
If an unknown method is called, an "unknownMethod" error (this is the<br />
type shown in the example above) MUST be inserted and then the next<br />
method call MUST be processed as normal.<br />
<br />
If an unknown argument or invalid arguments (wrong type, or in<br />
violation of other specified constraints) are supplied to a method,<br />
an "invalidArguments" error MUST be inserted and then the next method<br />
call MUST be processed as normal.<br />
<br />
3.4. Vendor-specific extensions<br />
<br />
Individual services will have custom features they wish to expose<br />
over JMAP. This may take the form of extra datatypes and/or methods<br />
not in the spec, or extra arguments to JMAP methods, or extra<br />
properties on existing data types (which may also appear in arguments<br />
to methods that take property names). To ensure compatibility with<br />
clients that don't know about a specific custom extension, and for<br />
compatibility with future versions of JMAP, the server MUST ONLY<br />
expose these extensions if the client explicitly opts in. Without<br />
opt-in, the server MUST follow the spec and reject anything that does<br />
not conform to it as specified.<br />
<br />
3.5. Security<br />
<br />
As always, the server must be strict about data received from the<br />
client. Arguments need to be checked for validity; a malicious user<br />
could attempt to find an exploit through the API. In case of invalid<br />
arguments (unknown/insufficient/wrong type for data etc.) the method<br />
MUST return an "invalidArguments" error and terminate.<br />
<br />
3.6. Concurrency<br />
<br />
To ensure the client always sees a consistent view of the data, the<br />
state accessed by a method call MUST NOT change during the execution<br />
of the method, except due to actions by the method call itself. The<br />
<br />
Jenkins Expires April 22, 2017 [Page 21]<br />
Internet-Draft JMAP October 2016<br />
<br />
state MAY change in-between method calls (even within a single API<br />
request).<br />
<br />
3.7. The Number datatype<br />
<br />
The JSON datatypes are limited to those found in JavaScript. A<br />
"Number" in JavaScript is represented as a signed double (64-bit<br />
floating point). However, except where explicitly specified, all<br />
numbers used in this API are unsigned integers <= 2^53 (the maximum<br />
integer that may be reliably stored in a double). This implicitly<br />
limits the maximum length of message lists in queries and the like.<br />
<br />
3.8. The Date datatypes<br />
<br />
Where a JMAP API specifies "Date" as a type, it means a string in<br />
[RFC3339] _date-time_ format, with the _time-offset_ component always<br />
"Z" (i.e. the date-time MUST be in UTC time) and _time-secfrac_<br />
always omitted. The "T" and "Z" MUST always be upper-case. For<br />
example, ""2014-10-30T14:12:00Z"".<br />
<br />
3.9. Use of null<br />
<br />
Unless otherwise specified, a missing property in the arguments<br />
object of a request (from the client), or a response (from the<br />
server) MUST be intepreted exactly the same as that property having<br />
the value "null".<br />
<br />
Unless otherwise specified, a missing property in a data object MUST<br />
be interpreted in the following ways: - In the response to a<br />
_getFoos_ style call, or when *creating* an object in a _setFoos_<br />
style call, a missing property MUST be interpreted as though it had<br />
the default value for that type, or "null" if no default is<br />
specified. - When *updating* an object in a _setFoos_ style call, a<br />
missing property MUST be interpreted as the existing value for that<br />
property (i.e. don't update it).<br />
<br />
For network efficiency, when fetching the server and client may make<br />
use of the above and omit properties which have the default value for<br />
the data type.<br />
<br />
3.10. CRUD methods<br />
<br />
JMAP provides a uniform interface for creating, retrieving, updating<br />
and deleting various types of objects. For a "Foo" data type,<br />
records of that type would be fetched via a "getFoos" call and<br />
modified via a "setFoos" call. Delta updates may be fetched via a<br />
"getFooUpdates" call. These methods all follow a standard format as<br />
described below.<br />
<br />
Jenkins Expires April 22, 2017 [Page 22]<br />
Internet-Draft JMAP October 2016<br />
<br />
3.10.1. getFoos<br />
<br />
Objects of type *Foo* are fetched via a call to _getFoos_. Methods<br />
with a name starting with "get" MUST NOT alter state on the server.<br />
<br />
This method may take some or all of the following arguments; see the<br />
definition of the data type in question. However, if one of the<br />
following arguments is available, it will behave exactly as specified<br />
below.<br />
<br />
o *accountId*: "String|null" The id of the Account to use. If<br />
"null", the primary account is used.<br />
<br />
o *ids*: "String[]|null" The ids of the Foo objects to return. If<br />
"null" then *all* records of the data type are returned, if this<br />
is supported for that data type.<br />
<br />
o *properties*: "String[]|null" If supplied, only the properties<br />
listed in the array are returned for each Foo object. If "null",<br />
all properties of the object are returned. The id of the object<br />
is *always* returned, even if not explicitly requested.<br />
<br />
The response to "getFoos" is called "foos". It has the following<br />
arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *state*: "String" A string representing the state on the server<br />
for *all* the data of this type. If the data changes, this string<br />
will change. It is used to get delta updates, if supported for<br />
the type.<br />
<br />
o *list*: "Foo[]" An array of the Foo objects requested. This is<br />
the *empty array* if no objects were found, or if the _ids_<br />
argument passed in was also the empty array.<br />
<br />
o *notFound*: "String[]|null" This array contains the ids passed to<br />
the method for records that do not exist. This property is "null"<br />
if all requested ids were found, or if the _ids_ argument passed<br />
in was either "null" or the empty array.<br />
<br />
The following error may be returned instead of the "foos" response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
Jenkins Expires April 22, 2017 [Page 23]<br />
Internet-Draft JMAP October 2016<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"requestTooLarge": Returned if the number of _ids_ requested by the<br />
client exceeds the maximum number the server is willing to process in<br />
a single method call.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
3.10.2. getFooUpdates<br />
<br />
When the state of the set of Foo records changes on the server<br />
(whether due to creation, updates or deletion), the _state_ property<br />
of the _foos_ response will change. The _getFooUpdates_ call allows<br />
a client to efficiently update the state of any its Foo cache to<br />
match the new state on the server. It takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the Account to use. If<br />
"null", the primary account is used.<br />
<br />
o *sinceState*: "String" The current state of the client. This is<br />
the string that was returned as the _state_ argument in the _foos_<br />
response. The server will return the changes made since this<br />
state.<br />
<br />
o *maxChanges*: "Number|null" The maximum number of Foo ids to<br />
return in the response. The server MAY choose to return fewer<br />
than this value, but MUST NOT return more. If not given by the<br />
client, the server may choose how many to return. If supplied by<br />
the client, the value MUST be a positive integer greater than 0.<br />
If a value outside of this range is given, the server MUST reject<br />
the call with an "invalidArguments" error.<br />
<br />
o *fetchRecords*: "Boolean|null" If "true", immediately after<br />
outputting the _fooUpdates_ response, the server will make an<br />
implicit call to _getFoos_ with the _changed_ property of the<br />
response as the _ids_ argument. If "false" or "null", no implicit<br />
call will be made.<br />
<br />
o *fetchRecordProperties*: "String[]|null" If the _getFoos_ method<br />
takes a _properties_ argument, this argument is passed through on<br />
implicit calls (see the _fetchRecords_ argument).<br />
<br />
Jenkins Expires April 22, 2017 [Page 24]<br />
Internet-Draft JMAP October 2016<br />
<br />
The response to _getFooUpdates_ is called _fooUpdates_. It has the<br />
following arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String" This is the _sinceState_ argument echoed<br />
back; the state from which the server is returning changes.<br />
<br />
o *newState*: "String" This is the state the client will be in after<br />
applying the set of changes to the old state.<br />
<br />
o *hasMoreUpdates*: "Boolean" If "true", the client may call<br />
_getFooUpdates_ again with the _newState_ returned to get further<br />
updates. If "false", _newState_ is the current server state.<br />
<br />
o *changed*: "String[]" An array of Foo ids for records which have<br />
been created or changed but not destroyed since the oldState.<br />
<br />
o *removed*: "String[]" An array of Foo ids for records which have<br />
been destroyed since the old state.<br />
<br />
The _maxChanges_ argument (and _hasMoreUpdates_ response argument) is<br />
available for data types with potentially large amounts of data (i.e.<br />
those for which there is a _getFooList_ method available for loading<br />
the data in pages). If a _maxChanges_ is supplied, or set<br />
automatically by the server, the server must try to limit the number<br />
of ids across _changed_ and _removed_ to the number given. If there<br />
are more changes than this between the client's state and the current<br />
server state, the update returned MUST take the client to an<br />
intermediate state, from which the client can continue to call<br />
_getFooUpdates_ until it is fully up to date. The server MUST NOT<br />
return more ids than the _maxChanges_ total. If the server is unable<br />
to calculate a suitable intermediate state, it MUST return a<br />
"cannotCalculateChanges" error.<br />
<br />
If a Foo record has been modified AND deleted since the oldState, the<br />
server SHOULD just return the id in the _removed_ response, but MAY<br />
return it in the changed response as well. If a Foo record has been<br />
created AND deleted since the oldState, the server SHOULD remove the<br />
Foo id from the response entirely, but MAY include it in the<br />
_removed_ response.<br />
<br />
The following errors may be returned instead of the _fooUpdates_<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
Jenkins Expires April 22, 2017 [Page 25]<br />
Internet-Draft JMAP October 2016<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
"invalidArguments": Returned if the request does not include one of<br />
the required arguments, or one of the arguments is of the wrong type,<br />
or otherwise invalid. A _description_ property MAY be present on the<br />
response object to help debug with an explanation of what the problem<br />
was.<br />
<br />
"cannotCalculateChanges": Returned if the server cannot calculate the<br />
changes from the state string given by the client. Usually due to<br />
the client's state being too old, or the server being unable to<br />
produce an update to an intermediate state when there are too many<br />
updates. The client MUST invalidate its Foo cache.<br />
<br />
3.10.3. setFoos<br />
<br />
Modifying the state of Foo objects on the server is done via the<br />
_setFoos_ method. This encompasses creating, updating and destroying<br />
Foo records. This has two benefits:<br />
<br />
1. It allows the server to sort out ordering and dependencies that<br />
may exist if doing multiple operations at once (for example to<br />
ensure there is always a minimum number of a certain record<br />
type).<br />
<br />
2. A single call can make all the changes you want to a particular<br />
type. If the client wants to use _ifInState_ to guard its<br />
changes, it can only make one call that modifies a particular<br />
type per request, since it will need the new state following that<br />
call to make the next modification.<br />
<br />
The _setFoos_ method takes the following arguments:<br />
<br />
o *accountId*: "String|null" The id of the Account to use. If<br />
"null", the primary account is used.<br />
<br />
o *ifInState*: "String|null" This is a state string as returned by<br />
the _getFoos_ method. If supplied, the string must match the<br />
current state, otherwise the method will be aborted and a<br />
"stateMismatch" error returned. If "null", any changes will be<br />
applied to the current state.<br />
<br />
o *create*: "String[Foo]|null" A map of _creation id_ (an arbitrary<br />
string set by the client) to Foo objects (containing all<br />
properties except the id, unless otherwise stated in the specific<br />
<br />
Jenkins Expires April 22, 2017 [Page 26]<br />
Internet-Draft JMAP October 2016<br />
<br />
documentation of the data type). If "null", no objects will be<br />
created.<br />
<br />
o *update*: "String[Foo]|null" A map of id to Foo objects. The<br />
object may omit any property; only properties that have changed<br />
need be included. If "null", no objects will be updated.<br />
<br />
o *destroy*: "String[]|null" A list of ids for Foo objects to<br />
permanently delete. If "null", no objects will be deleted.<br />
<br />
Each create, update or destroy is considered an atomic unit. It is<br />
permissible for the server to commit some of the changes but not<br />
others, however it is not permissible to only commit part of an<br />
update to a single record (e.g. update a _name_ property but not a<br />
_count_ property, if both are supplied in the update object).<br />
<br />
If a create, update or destroy is rejected, the appropriate error<br />
MUST be added to the notCreated/notUpdated/notDestroyed property of<br />
the response and the server MUST continue to the next create/update/<br />
destroy. It does not terminate the method.<br />
<br />
If an id given cannot be found, the update or destroy MUST be<br />
rejected with a "notFound" set error.<br />
<br />
Some record objects may hold references to others (foreign keys).<br />
When records are created or modified, they may reference other<br />
records being created _in the same API request_ by using the creation<br />
id prefixed with a "#". The order of the method calls in the request<br />
by the client MUST be such that the record being referenced is<br />
created in the same or an earlier call. The server thus never has to<br />
look ahead. Instead, while processing a request (a series of method<br />
calls), the server MUST keep a simple map for the duration of the<br />
request of creation id to record id for each newly created record, so<br />
it can substitute in the correct value if necessary in later method<br />
calls. Creation ids sent by the client SHOULD be unique within the<br />
single API request for a particular data type. If a creation id is<br />
reused, the server MUST map the creation id to the most recently<br />
created item with that id.<br />
<br />
The response to _setFoos_ is called _foosSet_. It has the following<br />
arguments:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
o *oldState*: "String|null" The state string that would have been<br />
returned by _getFoos_ before making the requested changes, or<br />
"null" if the server doesn't know what the previous state string<br />
was.<br />
<br />
Jenkins Expires April 22, 2017 [Page 27]<br />
Internet-Draft JMAP October 2016<br />
<br />
o *newState*: "String" The state string that will now be returned by<br />
_getFoos_.<br />
<br />
o *created*: "String[Foo]" A map of the creation id to an object<br />
containing any *server-assigned* properties of the Foo object<br />
(including the id) for all successfully created records.<br />
<br />
o *updated*: "String[]" A list of Foo ids for records that were<br />
successfully updated.<br />
<br />
o *destroyed*: "String[]" A list of Foo ids for records that were<br />
successfully destroyed.<br />
<br />
o *notCreated*: "String[SetError]" A map of creation id to a<br />
SetError object for each record that failed to be created. The<br />
possible errors are defined in the description of the method for<br />
specific data types.<br />
<br />
o *notUpdated*: "String[SetError]" A map of Foo id to a SetError<br />
object for each record that failed to be updated. The possible<br />
errors are defined in the description of the method for specific<br />
data types.<br />
<br />
o *notDestroyed*: "String[SetError]" A map of Foo id to a SetError<br />
object for each record that failed to be destroyed. The possible<br />
errors are defined in the description of the method for specific<br />
data types.<br />
<br />
A *SetError* object has the following properties:<br />
<br />
o *type*: "String" The type of error.<br />
<br />
o *description*: "String|null" A description of the error to display<br />
to the user.<br />
<br />
Other properties may also be present on the object, as described in<br />
the relevant methods.<br />
<br />
The following errors may be returned instead of the "foosSet"<br />
response:<br />
<br />
"accountNotFound": Returned if an _accountId_ was explicitly included<br />
with the request, but it does not correspond to a valid account.<br />
<br />
"accountNotSupportedByMethod": Returned if the _accountId_ given<br />
corresponds to a valid account, but the account does not support this<br />
data type.<br />
<br />
Jenkins Expires April 22, 2017 [Page 28]<br />
Internet-Draft JMAP October 2016<br />
<br />
"accountReadOnly": Returned if the account has isReadOnly == true.<br />
<br />
"requestTooLarge": Returned if the total number of objects to create,<br />
update or destroy exceeds the maximum number the server is willing to<br />
process in a single method call.<br />
<br />
"invalidArguments": Returned if one of the arguments is of the wrong<br />
type, or otherwise invalid. A "description" property MAY be present<br />
on the response object to help debug with an explanation of what the<br />
problem was.<br />
<br />
"stateMismatch": Returned if an "ifInState" argument was supplied and<br />
it does not match the current state.<br />
<br />
4. Downloading binary data<br />
<br />
Binary data is referenced by a _blobId_ in JMAP. A blob id does not<br />
have a name inherent to it, but this is normally given in the same<br />
object that contains the blob id.<br />
<br />
After completing authentication, the client will receive a<br />
_downloadUrl_ as part of the response. This is in [RFC6570] URI<br />
Template (level 1) format. The URL MUST contain variables called<br />
"accountId" and "blobId". The URL SHOULD contain a variable called<br />
"name".<br />
<br />
The client may use this template in combination with an accountId and<br />
blobId to download any binary data (files) referenced by other<br />
objects. Since a blob is not associated with a particular name, the<br />
template SHOULD allow a name to be substituted in as well; the server<br />
will return this as the filename if it sets a "Content-Disposition"<br />
header.<br />
<br />
To download the data the client MUST make an authenticated GET<br />
request to the download URL with the appropriate variables<br />
substituted in, and then follow any redirects. In situations where<br />
it's not easy to authenticate the download request (e.g.: when<br />
downloading a file through a link in a HTML document), the client MAY<br />
use a signed GET request (see below for how to issue a signed<br />
request).<br />
<br />
After following redirects, the server MUST return one of the<br />
following responses to a request to the download URL:<br />
<br />
Jenkins Expires April 22, 2017 [Page 29]<br />
Internet-Draft JMAP October 2016<br />
<br />
4.1. 200: OK<br />
<br />
Request successful. The binary data is returned. The "Content-Type"<br />
header SHOULD be set to the correct content type for the content.<br />
<br />
4.2. 401: Unauthorized<br />
<br />
The "Authorization" header was missing or did not contain a valid<br />
token and there was no "access_token" query parameter, or it did not<br />
contain a valid token. Reauthenticate and then retry the request.<br />
As per the HTTP spec, the response MUST have a "WWW-Authenticate"<br />
header listing the available authentication schemes.<br />
<br />
The server MAY return an HTML page response, which clients MAY show<br />
to the user. This is primarily for when the URL is passed off to the<br />
browser, and the JMAP client may not see the actual response.<br />
<br />
4.3. 404: Not Found<br />
<br />
The file was not found at this address.<br />
<br />
4.4. 503: Service Unavailable<br />
<br />
The server is currently down. The client should try again later with<br />
exponential backoff. There is no content in the response.<br />
<br />
5. Uploading binary data<br />
<br />
There is a single endpoint which handles all file uploads, regardless<br />
of what they are to be used for. To upload a file, the client<br />
submits a POST request to the file upload endpoint (see the<br />
authentication section for information on how to obtain this URL).<br />
The Content-Type MUST be correctly set for the type of the file being<br />
uploaded. The request MUST be authenticated as per any HTTP request.<br />
The request MAY include an "X-JMAP-AccountId" header, with the value<br />
being the account to use for the request. Otherwise, the default<br />
account will be used.<br />
<br />
The server will respond with one of the following HTTP response<br />
codes:<br />
<br />
5.1. 201: File uploaded successfully<br />
<br />
The content of the response is a single JSON object with the<br />
following properties:<br />
<br />
o *accountId*: "String" The id of the account used for the call.<br />
<br />
Jenkins Expires April 22, 2017 [Page 30]<br />
Internet-Draft JMAP October 2016<br />
<br />
o *blobId*: "String", The id representing the binary data uploaded.<br />
The data for this id is immutable. The id _only_ refers to the<br />
binary data, not any metadata.<br />
<br />
o *type*: "String" The content type of the file.<br />
<br />
o *size*: "Number" The size of the file in bytes.<br />
<br />
o *expires*: "Date" The date the file will be deleted from tempoary<br />
storage if not referenced by another object, e.g. used in a draft.<br />
<br />
Once the file has been used, for example attached to a draft message,<br />
the file will no longer expire, and is instead guaranteed to exist<br />
while at least one other object references it. Once no other object<br />
references it, the server MAY immediately delete the file at any<br />
time. It MUST NOT delete the file during the method call which<br />
removed the last reference, so that if there is a create and a delete<br />
within the same call that both reference the file, this always works.<br />
<br />
If uploading a file would take the user over quota, the server SHOULD<br />
delete previously uploaded (but unused) files before their expiry<br />
time. This means a client does not have to explicitly delete unused<br />
temporary files (indeed, there is no way for it to do so).<br />
<br />
If identical binary content is uploaded, the same _blobId_ SHOULD be<br />
returned.<br />
<br />
The server MUST return one of the following responses to a request to<br />
the upload URL:<br />
<br />
5.2. 400: Bad request<br />
<br />
The request was malformed (this includes the case where an "X-JMAP-<br />
AccountId" header is sent with a value that does not exist). The<br />
client SHOULD NOT retry the same request. There is no content in the<br />
response.<br />
<br />
5.3. 401: Unauthorized<br />
<br />
The "Authorization" header was missing or did not contain a valid<br />
token. Reauthenticate and then retry the request. As per the HTTP<br />
spec, the response MUST have a "WWW-Authenticate" header listing the<br />
available authentication schemes. There is no content in the<br />
response.<br />
<br />
Jenkins Expires April 22, 2017 [Page 31]<br />
Internet-Draft JMAP October 2016<br />
<br />
5.4. 404: Not Found<br />
<br />
The upload endpoint has moved. See the Authentication section of the<br />
spec for how to rediscover the current URL to use. There is no<br />
content in the response.<br />
<br />
5.5. 413: Request Entity Too Large<br />
<br />
The file is larger than the maximum size the server is willing to<br />
accept for a single file. The client SHOULD NOT retry uploading the<br />
same file. There is no content in the response. The client may<br />
discover the maximum size the server is prepared to accept by<br />
inspecting the server _capabilities_ object, returned with the<br />
successful authentication response.<br />
<br />
5.6. 415: Unsupported Media Type<br />
<br />
The server MAY choose to not allow certain content types to be<br />
uploaded, such as executable files. This error response is returned<br />
if an unacceptable type is uploaded. The client SHOULD NOT retry<br />
uploading the same file. There is no content in the response.<br />
<br />
5.6.1. 429: Rate limited<br />
<br />
Returned if the client has made too many upload requests recently, or<br />
has too many concurrent uploads currently in progress. Clients<br />
SHOULD wait a while then try again. The response MAY include a<br />
"Retry-After" header indicating how long to wait before making a new<br />
request.<br />
<br />
5.7. 503: Service Unavailable<br />
<br />
The server is currently down. The client should try again later with<br />
exponential backoff. There is no content in the response.<br />
<br />
6. Push<br />
<br />
Any modern email client should be able to update instantly whenever<br />
the data on the server is changed by another client or message<br />
delivery. Push notifications in JMAP occur out-of-band (i.e. not<br />
over the same connection as API exchanges) so that they can make use<br />
of efficient native push mechanisms on different platforms.<br />
<br />
The general model for push is simple and does not send any sensitive<br />
data over the push channel, making it suitable for use with less<br />
trusted 3rd party intermediaries. The format allows multiple changes<br />
to be coalesced into a single push update, and the frequency of<br />
pushes to be rate limited by the server. It doesn't matter if some<br />
<br />
Jenkins Expires April 22, 2017 [Page 32]<br />
Internet-Draft JMAP October 2016<br />
<br />
push events are dropped before they reach the client; it will still<br />
get all changes next time it syncs.<br />
<br />
When something changes on the server, the server pushes a small JSON<br />
object to the client with the following property:<br />
<br />
o *changed*: "String[ChangedStates]" A map of _account id_ to an<br />
object encoding the state of data types which have changed for<br />
that account since the last push event, for each of the accounts<br />
to which the user has access and for which something has changed.<br />
<br />
A *ChangedStates* object is a map of the type name (e.g. "Mailbox"<br />
or "Message") to the current state token for that type (i.e. the<br />
"state" property that would currently be returned by a call to<br />
"getMailboxes" or "getMessages", as appropriate). The types in JMAP<br />
are "Mailbox", "Thread", "Message", "ContactGroup", "Contact",<br />
"Calendar", "CalendarEvent".<br />
<br />
Upon receiving this data, the client can compare the new state<br />
strings with its current values to see whether it has the current<br />
data for these types. The actual changes can then be efficiently<br />
fetched in a single standard API request (using the _getFooUpdates_<br />
type methods).<br />
<br />
6.1. Event Source<br />
<br />
There are two mechanisms by which the client can receive the push<br />
events. The first is directly via a "text/event-stream" resource, as<br />
described in <http://www.w3.org/TR/eventsource/>. This is<br />
essentially a long running HTTP request down which the server can<br />
push data. When a change occurs, the server MUST push an event<br />
called *state* to any connected clients.<br />
<br />
The server MAY also set a new "Last-Event-Id" that encodes the entire<br />
server state visible to the user. When a new connection is made to<br />
the event-source endpoint, the server can then work out whether the<br />
client has missed some changes which it should send immediately.<br />
<br />
The server MUST also send an event called *ping* with an empty object<br />
as the data if a maximum of 5 minutes has elapsed since the previous<br />
event. This MUST NOT set a new "Last-Event-Id". A client may detect<br />
the absence of these to determine that the HTTP connection has been<br />
dropped somewhere along the route and so it needs to re-establish the<br />
connection.<br />
<br />
Refer to the Authentication section of this spec for details on how<br />
to get the URL for the event-source endpoint. The request must be<br />
authenticated using an "Authorization" header like any HTTP request.<br />
<br />
Jenkins Expires April 22, 2017 [Page 33]<br />
Internet-Draft JMAP October 2016<br />
<br />
A client MAY hold open multiple connections to the event-source,<br />
although it SHOULD try to use a single connection for efficiency.<br />
<br />
6.2. Web hook<br />
<br />
The second push mechanism is to register a callback URL to which the<br />
JMAP server will make an HTTPS POST request whenever the event<br />
occurs. The request MUST have a content type of "application/json"<br />
and contain the same UTF-8 JSON encoded object as described above as<br />
the body.<br />
<br />
The JMAP server MUST also set the following headers in the POST<br />
request: - "X-JMAP-EventType: state" - "X-JMAP-User: ${username}"<br />
where "${username}" is the username of the authenticated user for<br />
which the push event occurred.<br />
<br />
The JMAP server MUST follow any redirects. If the final response<br />
code from the server is "2xx", the callback is considered a success.<br />
If the response code is "503" (Service Unavailable), the JMAP server<br />
MAY try again later (but may also just drop the event). If the<br />
response code is "429" (Too Many Requests) the JMAP server SHOULD<br />
attempt to reduce the frequency of pushes to that URL. Any other<br />
response code MUST be considered a *permanent failure* and the<br />
callback should be deregistered (not tried again even for future<br />
events unless explicitly re-registered by the client).<br />
<br />
The URL set by the client MUST use the HTTPS protocol and SHOULD<br />
encode within it a unique token that can be verified by the server to<br />
know that the request comes from the JMAP server the authenticated<br />
client connected to.<br />
<br />
The callback is tied to the access token used to create it. Should<br />
the access token expire or be revoked, the callback MUST be removed<br />
by the JMAP server. The client MUST re-register the callback after<br />
reauthenticating to resume callbacks.<br />
<br />
Each session may only have a single callback URL registered. It can<br />
be set or retrieved using the following API calls.<br />
<br />
6.2.1. setPushCallback<br />
<br />
To set the web hook, make a call to _setPushCallback_. It takes the<br />
following argument:<br />
<br />
o *callback*: "String|null" The (HTTPS) URL the JMAP server should<br />
POST events to. This will replace any previously set URL. Set to<br />
"null" to just remove any previously set callback URL.<br />
<br />
Jenkins Expires April 22, 2017 [Page 34]<br />
Internet-Draft JMAP October 2016<br />
<br />
The response to _setPushCallback_ is called _pushCallbackSet_. It has<br />
the following argument:<br />
<br />
o *callback*: "String|null" Echoed back from the call.<br />
<br />
The following error may be returned instead of the _mailboxesSet_<br />
response:<br />
<br />
"invalidUrl": Returned if the URL does not begin with "https://", or<br />
is otherwise syntactically invalid or does not resolve.<br />
<br />
6.2.2. getPushCallback<br />
<br />
To check the currently set callback URL (if any), make a call to<br />
_getPushCallback_. It does not take any arguments. The response to<br />
_getPushCallback_ is called "pushCallback". It has a single<br />
argument:<br />
<br />
o *callback*: "String|null" The URL the JMAP server is currently<br />
posting push events to, or "null" if none.<br />
<br />
7. References<br />
<br />
7.1. Normative References<br />
<br />
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate<br />
Requirement Levels", BCP 14, RFC 2119,<br />
DOI 10.17487/RFC2119, March 1997,<br />
<http://www.rfc-editor.org/info/rfc2119>.<br />
<br />
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:<br />
Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,<br />
<http://www.rfc-editor.org/info/rfc3339>.<br />
<br />
[RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform<br />
Resource Identifier (URI): Generic Syntax", STD 66,<br />
RFC 3986, DOI 10.17487/RFC3986, January 2005,<br />
<http://www.rfc-editor.org/info/rfc3986>.<br />
<br />
[RFC5785] Nottingham, M. and E. Hammer-Lahav, "Defining Well-Known<br />
Uniform Resource Identifiers (URIs)", RFC 5785,<br />
DOI 10.17487/RFC5785, April 2010,<br />
<http://www.rfc-editor.org/info/rfc5785>.<br />
<br />
[RFC6186] Daboo, C., "Use of SRV Records for Locating Email<br />
Submission/Access Services", RFC 6186,<br />
DOI 10.17487/RFC6186, March 2011,<br />
<http://www.rfc-editor.org/info/rfc6186>.<br />
<br />
Jenkins Expires April 22, 2017 [Page 35]<br />
Internet-Draft JMAP October 2016<br />
<br />
[RFC6570] Gregorio, J., Fielding, R., Hadley, M., Nottingham, M.,<br />
and D. Orchard, "URI Template", RFC 6570,<br />
DOI 10.17487/RFC6570, March 2012,<br />
<http://www.rfc-editor.org/info/rfc6570>.<br />
<br />
[RFC6750] Jones, M. and D. Hardt, "The OAuth 2.0 Authorization<br />
Framework: Bearer Token Usage", RFC 6750,<br />
DOI 10.17487/RFC6750, October 2012,<br />
<http://www.rfc-editor.org/info/rfc6750>.<br />
<br />
[RFC6764] Daboo, C., "Locating Services for Calendaring Extensions<br />
to WebDAV (CalDAV) and vCard Extensions to WebDAV<br />
(CardDAV)", RFC 6764, DOI 10.17487/RFC6764, February 2013,<br />
<http://www.rfc-editor.org/info/rfc6764>.<br />
<br />
[RFC7159] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data<br />
Interchange Format", RFC 7159, DOI 10.17487/RFC7159, March<br />
2014, <http://www.rfc-editor.org/info/rfc7159>.<br />
<br />
[RFC7493] Bray, T., Ed., "The I-JSON Message Format", RFC 7493,<br />
DOI 10.17487/RFC7493, March 2015,<br />
<http://www.rfc-editor.org/info/rfc7493>.<br />
<br />
7.2. URIs<br />
<br />
[1] https://tools.ietf.org/html/rfc6238<br />
<br />
[2] https://developers.yubico.com/OTP/<br />
<br />
[3] https://fidoalliance.org/specifications/download/<br />
<br />
[4] https://jwt.io/<br />
<br />
Author's Address<br />
<br />
Neil Jenkins<br />
FastMail<br />
Level 1, 91 William St<br />
Melbourne VIC 3000<br />
Australia<br />
<br />
Email: neilj@fastmail.com<br />
URI: https://www.fastmail.com<br />
<br />
Jenkins Expires April 22, 2017 [Page 36]</div>
Sysop