Développer une application mobile de messagerie en temps réel avec React Native

En ce qui concerne les applications mobiles, le temps réel est l'une des caractéristiques les plus utiles pour rendre votre application plus efficace pour l'utilisateur final.

Read this article in english

Ce guide est la première partie d'une série de 3 articles qui vous aideront à comprendre comment développer une application de messagerie mobile en temps réel avec Kuzzle Mobile et React Native (largement inspiré par le getting-started écrit par Esteban.B dans la documentation de Kuzzle).

 

Nous allons essayer d'être très exhaustifs et détailler les meilleures pratiques que nous avons apprises avec toute l'équipe de Kuzzle à travers de nombreux projets.

Vous êtes prêt ? C'est parti !

 

 

 

Conditions préalables 

Dans ce tutoriel, nous allons utiliser React Native en liaison avec Expo. Pour ceux qui ne sont pas familiers avec Expo, il s'agit d'une plateforme open-source permettant de créer des applications natives pour Android et iOS en utilisant React-Native.

De plus, pour faire fonctionner une instance de Kuzzle Mobile, nous aurons besoin de Docker et Docker-compose. Notez qu'il est fortement recommandé d'utiliser Linux ou Mac OS comme système d'exploitation.

 

Si vous ne disposez pas encore de tous ces outils, vous pouvez consulter ces tutoriels en anglais :

 

Si vous souhaitez exécuter et tester l'application sur un appareil réel (ce que je recommande), vous devrez installer l'application Expo sur votre téléphone.

Concernant Kuzzle Mobile, je vous recommande de l'exécuter une fois en suivant ce getting-started sur notre documentation. Il vous fera utiliser le script d'installation et vérifiera que tout est correct avant que vous ne puissiez commencer ce guide.

Initialiser le projet

Une fois tous ces outils mis en place, c'est le moment de créer un nouveau projet d'application mobile !

Expo et React Native

Lancez un terminal, allez où vous voulez créer le projet sur votre système de fichiers et tapez simplement :

 

expo init messagingApp

 

(bien sûr, vous pouvez choisir un autre nom)
Le CLI va vous demander quel modèle utiliser, il suffit de prendre la première option et d'appuyer sur la touche entrée.

 

 

 

Une fois que tout est téléchargé et installé, il suffit de suivre les instructions d'Expo et de vous rendre dans le dossier de votre nouveau projet.


Nous devons maintenant installer quelques dépendances pour nous aider à développer notre application. En voici la liste :

  • kuzzle-sdk: le SDK javascript officiel de Kuzzle ! Il permet de communiquer avec un serveur Kuzzle à partir d'applications Javascript utilisant des protocoles.
  • native-base: composant cross-platforme UI essentiel pour React Native
  • expo-secure-store: qui fournit un moyen de chiffrer et de stocker en toute sécurité des paires key-value localement sur l'appareil

Pour les installer toutes, tapez cette commande dans votre terminal et attendez que tout soit configuré :

 

npm i kuzzle-sdk native-base expo-secure-store

 

Il est temps de lancer votre éditeur de code préféré (nous n'allons pas débattre ici pour savoir lequel est le meilleur😉 ).

Et si vous ouvrez App.js, vous verrez quelques exemples de codes. C'est le point d'entrée de notre application.
Vérifiez si tout fonctionne correctement en exécutant cette commande :

 

npm start

 

Cela ouvrira une nouvelle fenêtre de navigateur avec tous les logs de l'application et d'autres options. Et aussi dans votre terminal, vous verrez un QRcode, scannez-le avec l'application Expo sur votre appareil pour faire fonctionner notre application fraîchement créée !

 

 

 

Et tadaa ! Devant vos yeux, voici notre application ! Oui, d'accord, ça ne fait pas grand-chose pour le  moment, mais soyez patients.

 

 

 

Docker-compose

La prochaine étape à franchir pour initialiser notre projet est le lancement d'une instance de Kuzzle Mobile.
Pour ce faire, il suffit de créer un fichier docker-compose.yml à la racine du projet et d'ajouter ces lignes :

 

version: "3"

services:
  kuzzle:
    image: kuzzleio/kuzzle:2
    ports:
      - "7512:7512"
      - "1883:1883"
    cap_add:
      - SYS_PTRACE
    depends_on:
      - redis
      - elasticsearch
    environment:
      - kuzzle_services__storageEngine__client__node=http://elasticsearch:9200
      - kuzzle_services__internalCache__node__host=redis
      - kuzzle_services__memoryStorage__node__host=redis
      - kuzzle_server__protocols__mqtt__enabled=true
      - NODE_ENV=production

  redis:
    image: redis:5

  elasticsearch:
    image: kuzzleio/elasticsearch:7.4.0
    ulimits:
      nofile: 65536

volumes:
  es-data:
    driver: local

 

 

Notez que Kuzzle utilise Elasticsearch pour le stockage des données et Redis pour la gestion du cache interne. C'est pourquoi nous déclarons 3 services dans notre fichier docker-compose. Nous ajoutons également un volume docker pour que toutes nos données persistent même si nous supprimons les conteneur.

 

Vérifiez que votre instance se lance correctement en exécutant :

 

docker-compose up

 

Vous devriez voir que Kuzzle Mobile est prêt après la séquence de lancement.

 

 

 

Configurer Kuzzle Mobile

Maintenant que notre instance est lancée et que le projet est initialisé, nous pouvons passer à la configuration.

 

Kuzzle Admin Console

Nous fournissons une console d'administration, hébergée par nos soins. Mais ne vous inquiétez pas, ce n'est qu'une application statique fonctionnant sur votre navigateur, aucune donnée n'est conservée, et cette console se connecte uniquement via votre propre réseau à votre instance Kuzzle. 

Pour y accéder, il suffit de suivre ce lien :
http://console.kuzzle.io/kuzzle-v2/

 

Si c'est la première fois que vous venez ici, il vous sera demandé de créer un nouvel environnement pour vous connecter à votre serveur Kuzzle Mobile en cours d'exécution.

 

Il suffit de remplir le formulaire en donnant un nom à votre environnement. Dans la zone "hôte", donnez l'URL ou le nom d'hôte pour accéder à votre instance ("localhost" dans notre cas). Conservez également le port par défaut et décochez l'option "use SSL".
Soumettez ce formulaire en cliquant sur le bouton "Create".

 

 

 

 

Suivez les instructions sur votre écran pour créer un compte d'administrateur et n'oubliez pas de cocher "Remove anonymous credentials".

 

 

 

Après vous être connecté avec votre nouveau compte administrateur, vous pouvez voir l'interface principale de la console d'administration.

 

Gérer vos données

Il est maintenant temps de poser le clavier et de lire une petite explication sur la façon dont nous organisons nos données avec Kuzzle !

Comme je l'ai mentionné plus haut, nous utilisons Elasticsearch pour stocker les documents. Tous les documents, y compris les documents Kuzzle internes (comme les informations de sécurité), sont stockés dans les index Elasticsearch.

Le stockage des données est organisé en 4 niveaux :

  • index 
  • collections
  • documents
  • champs

Un index rassemble plusieurs collections, qui à leur tour contiennent plusieurs documents, chacun d'entre eux étant composé de plusieurs champs.

 

 

 

Si ce principe est appliqué à notre application de messagerie, nous avons besoin d'un seul index pour l'ensemble de l'application contenant une seule collection, celle-ci stockera tous les messages envoyés par les utilisateurs sous forme de documents.

 

Donc, retournez sur la console d'administration et cliquez sur le bouton "Créer un index" et donnez-lui un nom (quelque chose comme "messaging-app").

 

 

 

Une fois fait, vous pouvez maintenant créer une nouvelle collection. Cliquez sur le bouton "Create a collection".
Et... laissez-moi vous présenter la notion de "mappings".

 

Comme il est dit dans l'Admin Console  :

 

“Le Mapping sert à définir comment un document, et les champs qu'il contient, sont stockés et indexés".

 

Pour en savoir plus sur les mappings ES et tous les types pris en charge cliquez ici.

 

Le Mapping dont nous avons besoin pour stocker les messages des utilisateurs est vraiment simple, nous n'avons besoin que du contenu du message et de son auteur.
Voici un exemple :

 

{
  "content": { "type": "text" },
  "author": { "type": "keyword" }
}

 

Notez que la différence entre les types "text" et "keyword" concerne principalement la façon dont Elasticsearch indexe le contenu du document.

 

Pour le type keyword, nous pouvons effectuer des opérations de tri et de correspondance exacte du contenu du champ, et si nous voulons une correspondance floue à l'intérieur d'un texte long, nous devons spécifier un type text.
Créez la nouvelle collection avec ce tri et donnez-lui un nom comme "messages". Validez le formulaire et nous en avons fini avec la gestion des données pour le moment.

 

Droits et autorisations 

Pour la prochaine étape, nous allons nous concentrer sur nos utilisateurs. C'est la partie où nous allons configurer les droits et autorisations.

 

La couche de sécurité de Kuzzle relie les utilisateurs à un ou plusieurs profils. Vous pouvez considérer un profil comme un groupe d'utilisateurs qui partagent les mêmes autorisations.

 

Les profils eux-mêmes sont constitués de différents groupes d'autorisations, ces groupes sont appelés rôles.

Un profil est lié à un ensemble de rôles, et chaque rôle définit un ensemble d'autorisations. Par exemple, dans le diagramme ci-dessous, le profil de l'éditeur (editor) a toutes les autorisations, le contributeur  (contributor) a un sous-ensemble des autorisations, et le profil par défaut (default) n'a que les autorisations par défaut :

 

 

 

 

Allez dans le menu "Sécurity" de la console d'administration, puis cliquez sur le lien "Roles" dans le menu de gauche.
Nous allons d'abord restreindre le rôle "anonymous" afin qu'il ne puisse effectuer que le minimum d'actions pour s'authentifier.
Modifiez ce rôle en cliquant sur le crayon et donnez-lui cette règle :

 

{
  "controllers": {
    "auth": {
      "actions": {
        "login": true,
        "checkToken": true
      }
    }
  }
}

 

Notez que les autorisations de sécurité se comportent comme une liste blanche, donc tout ce qui n'est pas autorisé est interdit.

 

 

 

 

Validez en cliquant sur le bouton "update".

Ensuite, nous devons créer un nouveau rôle, qui donnera à nos utilisateurs connectés les autorisations d'authentification nécessaires. Cliquez sur le bouton "create" et donnez-lui le nom "authentication" et ce ce contenu:

 

{
  "controllers": {
    "auth": {
      "actions": {
        "*": true
      }
    }
  }
}

 

 

 

 

Une fois de plus, validez le formulaire. Et maintenant, nous pouvons aller à la page "profiles" en cliquant sur le menu de gauche.
Créez un nouveau profil, appelez-le "standard-user" et donnez-lui ce contenu

 

{
  "rateLimit": 0,
  "policies": [
    {
      "roleId": "authentication"
    }
  ]
}

 

 

 

 

Soumettez le formulaire en cliquant sur le bouton "create" et notre nouveau profil bénéficie désormais des autorisations définies dans le rôle "authentication".

Et, pour l'étape suivante, il nous suffit de créer un nouvel utilisateur. Cliquez sur le lien "Users" dans la barre de gauche puis sur le bouton "Create" et remplissez la première partie du formulaire en cochant la case "Auto-generate" pour le KUID. Cela permettra à Kuzzle de générer un identifiant unique. Ajoutez également le profil "standard-user" puis cliquez sur le bouton "Next".

 

 

 

Pour les parties suivantes du formulaire, nous définirons les informations d'identification de notre nouvel utilisateur. Choisissez un nom d'utilisateur et un mot de passe (souvenez-vous en, nous en aurons besoin pour tester l'application) et cliquez sur le bouton "Next".

 

 

 

Pour terminer la création du nouvel utilisateur, il suffit de cliquer sur le bouton "Save".
Vous serez redirigé vers la liste des utilisateurs et vous pourrez alors voir le nouvel utilisateur que vous venez de créer.
La configuration est terminée pour l'instant. C'est le bon moment pour passer à la partie suivante !

 

Allez on code !

Prenez un café et une grande respiration, il est temps de coder !
Nous allons connecter notre application à l'instance Kuzzle et créer notre propre formulaire de connexion.

 

Les composants principaux

Prenez à nouveau votre éditeur de code préféré et nous pouvons maintenant passer au composant principal de notre application. Ouvrez App.js et supprimez tout le code qui y est présent, nous n'en avons plus besoin.
La première chose à faire est de définir la déclaration d'importation en haut de notre fichier et d'initialiser une fonction principale vide :

 

import React, { useState, useEffect } from "react";

import * as Font from "expo-font";

import { AppLoading } from "expo";

import {
  Root,
  Header,
  Body,
  Title,
  Container,
  Toast,
  Text,
  Spinner,
} from "native-base";

export default function App() {}

 

 

Une fois que cela sera fait, nous pourrons commencer à initialiser notre application. Les composants Native-base doivent charger les polices avant toute chose. Heureusement pour nous, Expo fournit un moyen simple de le faire que nous couplerons à une variable de notre état et à un React State Hook.

Pour ce faire, en haut de la fonction App, définissez une variable d'état avec la méthode useState :

 

const [isRessourcesLoaded, setIsRessourcesLoaded] = useState(false);

 

(Pour en savoir plus sur les React State Hook, cliquez ici)

 

Nous avons également besoin des nouvelles fonctions pour charger nos polices :

 

const showToast = (type, message) => {
  return Toast.show({
    text: message,
    duration: 8000,
    type: type,
  });
};

const loadRessources = async () => {
  await Promise.all([
    Font.loadAsync({
      Roboto: require("native-base/Fonts/Roboto.ttf"),
      Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf"),
    }),
  ]);
};

const onLoadingError = () => {
  showToast(
    "danger",
    "Sorry an error occurred while the application is loading"
  );
};

 

La première permet d'afficher un "error toast" à notre utilisateur. En effet, il est de bonne pratique d'afficher un message d'erreur personnalisé compréhensible si quelque chose ne fonctionne pas.

 

La deuxième fonction charge toutes les polices nécessaires pour exécuter correctement l'application.

 

Et la troisième fonction permet, en cas d'erreur lors du chargement des polices, c'est celle-ci qui appelera la fonction "showToast" si besoins.

 

Maintenant, nous pouvons ajouter la fonction de rendu et la déclaration de retour :

 

const renderApp = () => {
  if (!isRessourcesLoaded) {
    return (
      <AppLoading
        startAsync={loadRessources}
        onError={onLoadingError}
        onFinish={() => setIsRessourcesLoaded(true)}
      />
    );
  }

  return (
    <Root>
      <Container>
        <Header>
          <Body>
            <Title>Kuzzle Chat</Title>
          </Body>
        </Header>

        <Container padder>
          <Text>Hello World !</Text>
        </Container>
      </Container>
    </Root>
  );
};

return renderApp();

 

Faisons fonctionner notre application avec Expo en exécutant :

 

npm start

 

Et scannez le QRcode avec l'application Expo sur votre téléphone pour voir le résultat

 

 

 

Se connecter à Kuzzle Mobile

Pour connecter notre application à notre instance Kuzzle Mobile en cours, nous utiliserons le kuzzle-sdk précédemment installé. Nous allons créer un service pour instancier le SDK et l'importer facilement là où il est nécessaire.
A la racine du projet, créer un nouveau dossier appelé "services" et ajouter dans celui-ci un nouveau fichier appelé kuzzle.js. Vous pouvez maintenant ajouter ces lignes :

 

import { Kuzzle, WebSocket } from "kuzzle-sdk";

export default new Kuzzle(new WebSocket("192.168.0.47"), {}); //put your local Kuzzle IP here

 

Ce que nous faisons ici est simplement d'instancier le SDK avec le protocole WebSocket afin de pouvoir utiliser les fonctionnalités en temps réel à l'avenir.

N'oubliez pas de remplacer l'adresse IP par celle de votre propre machine (cela est nécessaire pour tester l'application depuis votre propre appareil).

 

Retournez au fichier App.js et ajoutez l'importation du nouveau service que nous venons de créer :

 

import kuzzle from "./services/kuzzle";

 

Nous pouvons maintenant ajouter 2 nouvelles valeurs dans notre état avec la méthode useState pour gérer la connexion à notre serveur Kuzzle et vérifier que tout se charge bien dans l'application

 

const [connected, setConnected] = useState(false);
const [isLoadingComplete, setisLoadingComplete] = useState(false);

 

Nous avons maintenant besoin d'une nouvelle fonction pour nous connecter à notre instance Kuzzle :

 

const connectToKuzzle = async () => {
  try {
    await kuzzle.connect();
  } catch (err) {
    setConnected(false);

    showToast(
      "danger",
      "It looks like you're not connected to Kuzzle Mobile. Trying to reconnect..."
    );
  }
};

 

Il faut également savoir que le SDK Kuzzle peut lancer certains événements au cas où il serait déconnecté et qu'il peut aussi, par défaut, effectuer une reconnexion automatique si le réseau est perdu et retrouvé. Ce qui est une très bonne chose pour les applications mobiles.

Créons donc une nouvelle fonction pour gérer tous ces événements :

 

const handleKuzzleEvents = () => {
  kuzzle.on("connected", () => {
    setConnected(true);
  });

  kuzzle.on("reconnected", () => {
    setConnected(true);
  });

  kuzzle.on("disconnected", () => {
    setConnected(false);

    showToast(
      "danger",
      "It looks like you're not connected to Kuzzle Mobile. Trying to reconnect..."
    );
  });
};

 

Il est temps d'ajouter quelques hooks avec les méthodes useEffects, celles-ci seront appelées une fois que la valeur d'une variable, donnée en paramètre, sera mise à jour

 

useEffect(() => {
  if (isRessourcesLoaded) {
    handleKuzzleEvents();
    connectToKuzzle();
  }
}, [isRessourcesLoaded]);

useEffect(() => {
  if (connected) {
    setisLoadingComplete(true);
  }
}, [connected]);

 

En résumé, l'application va d'abord charger ce qui est nécessaire pour l'affichage (c'est-à-dire les polices) puis, une fois terminé, elle se connectera à Kuzzle et enfin basculera l'état de chargement complet.

N'oubliez pas de mettre à jour la fonction de rendu comme ceci :

 

const renderApp = () => {
  if (!isRessourcesLoaded) {
    return (
      <AppLoading
        startAsync={loadRessources}
        onError={onLoadingError}
        onFinish={() => setIsRessourcesLoaded(true)}
      />
    );
  }

  let pageContent = null;

  if (!isLoadingComplete && isRessourcesLoaded) {
    pageContent = <Spinner />;
  } else {
    pageContent = <Text>Hello World !</Text>;
  }

  return (
    <Root>
      <Container>
        <Header>
          <Body>
            <Title>Kuzzle Chat</Title>
          </Body>
        </Header>

        <Container padder>{pageContent}</Container>
      </Container>
    </Root>
  );
};

 

Nous conditionnons le contenu de la page en fonction de l'état de l'application et nous affichons un "spinner" à la place du contenu si nous ne sommes pas connectés à une instance Kuzzle.
Vérifions avec Expo pour qu'il n'y a pas d'erreur lors de la construction du projet.
S'il n'y a pas d'erreurs, nous pouvons alors passer à l'étape suivante !

 

Formulaire de connexion

Pour l'instant, créez un autre dossier à la racine du projet et appelez-le "components". Et bien sûr, dans ce dossier, créez un nouveau fichier appelé LoginForm.js

 

Et voici à quoi va ressembler ce composant :

 

import React, { useState, useEffect } from "react";
import {
  Form,
  Item,
  Input,
  Label,
  Button,
  Text,
  Toast,
  Content,
} from "native-base";
import kuzzle from "../services/kuzzle";

export default function LoginForm({ onLoginSuccess }) {
  const [username, setUsername] = useState(null);
  const [isUsernameEmpty, setIsUsernameEmpty] = useState(false);

  const [password, setPassword] = useState(null);
  const [isPasswordEmpty, setIsPasswordEmpty] = useState(false);

  const [canPerformLogin, setCanPerformLogin] = useState(false);

  const validateForm = async () => {
    let isFormValid = true;
    setIsUsernameEmpty(false);
    setIsPasswordEmpty(false);

    if (!username) {
      setIsUsernameEmpty(true);
      isFormValid = false;
    }

    if (!password) {
      setIsPasswordEmpty(true);
      isFormValid = false;
    }

    setCanPerformLogin(isFormValid);
  };

  const performLogin = async () => {
    let jwt = null;
    console.log("perform login");
    try {
      jwt = await kuzzle.auth.login("local", {
        username,
        password,
      });

      onLoginSuccess(jwt, username);
    } catch (err) {
      showToast("danger", err.message);
    }
  };

  const showToast = (type, message) => {
    return Toast.show({
      text: message,
      duration: 5000,
      type: type,
    });
  };

  useEffect(() => {
    if (canPerformLogin) {
      performLogin();
    }
  }, [canPerformLogin]);

  return (
    <Content>
      <Form>
        <Item floatingLabel error={isUsernameEmpty}>
          <Label>Username</Label>
          <Input onChangeText={(username) => setUsername(username)} />
        </Item>
        <Item floatingLabel error={isPasswordEmpty}>
          <Label>Password</Label>
          <Input
            secureTextEntry={true}
            onChangeText={(password) => setPassword(password)}
          />
        </Item>
        <Button
          block
          onPress={validateForm}
          style={{
            marginTop: 32,
          }}
        >
          <Text>Login</Text>
        </Button>
      </Form>
    </Content>
  );
}

 

Ce n'est pas un composant très compliqué. Ce que nous avons ici, c'est :

 

  • En haut du fichier, toutes les importations nécessaires
  • La fonction principale du composant, notez que nous ajoutons un paramètre, ce sera une fonction du composant parent, appelée une fois que l'action de connexion sera effectuée
  • Tous les "getters" et "setters" de notre "state"
  • Une fonction pour valider le formulaire et une autre pour montrer un toast en cas d'erreur,
  • Un hook avec la méthode useEffect pour appeler la fonction de login avec le SDK Kuzzle pour la stratégie d'authentification locale
  • Et pour finir, la déclaration de retour avec  le JSX pour notre formulaire

Enfin, la dernière étape consiste à mettre à jour les principaux éléments de l'application. Retournez au fichier App.js et ajoutez de nouvelles variables à notre state :

 

const [jwt, setJwt] = useState(null);
const [username, setUsername] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);

 

Ajoutez également une nouvelle fonction pour gérer l'action de réussite de la connexion à partir du composant enfant :

 

const onLoginSuccess = (jwt, username) => {
  setJwt(jwt);
  setUsername(username);
};

 

Ajoutez également un autre hook useEffect pour définir correctement la variable d'état correspondante :

 

useEffect(() => {
  if (jwt && username) {
    setIsLoggedIn(true);
  }
}, [jwt, username]);

 

Et enfin, une fois de plus, n'oubliez pas de mettre à jour la méthode de rendu

 

const renderApp = () => {
  if (!isRessourcesLoaded) {
    return (
      <AppLoading
        startAsync={loadRessources}
        onError={onLoadingError}
        onFinish={() => setIsRessourcesLoaded(true)}
      />
    );
  }

  let pageContent = null;

  if (!isLoadingComplete && isRessourcesLoaded) {
    pageContent = <Spinner />;
  } else if (!isLoggedIn) {
    pageContent = <LoginForm onLoginSuccess={onLoginSuccess} />;
  } else {
    pageContent = <Text>Hello {username}</Text>;
  }

  return (
    <Root>
      <Container>
        <Header>
          <Body>
            <Title>Kuzzle Chat</Title>
          </Body>
        </Header>
        <Container padder>{pageContent}</Container>
      </Container>
    </Root>
  );
};

 

Une fois encore, essayez de savoir si tout fonctionne en faisant tourner le projet sur votre téléphone avec Expo. Essayez de vous connecter avec l'utilisateur que vous avez créé auparavant. Si tout fonctionne, vous verrez "Hello" suivi du nom d'utilisateur de votre utilisateur.

 

 

 

 

 

 

 

 

Conclusion

Nous avons terminé la première partie de ce guide sur React Native et Kuzzle Mobile.

Je sais que ce n'est pas la partie la plus drôle, mais nous avons exploré beaucoup de concepts importants nécessaires pour la suite.

Nous avons appris à lancer une instance Kuzzle, à configurer des index et des collections mais aussi des droits pour vos utilisateurs. Et bien sûr, comment démarrer un projet et comment disposer d'un formulaire de connexion pour authentifier les utilisateurs avec Kuzzle Mobile.

Dans la prochaine partie, nous verrons comment créer le chat en temps réel et l'ajouter à notre application.

Si vous avez des questions ou si vous avez besoin d'un peu d'aide, n'hésitez pas à rejoindre notre serveur officiel Kuzzle Community Discord.

Je remercie tout particulièrement l'équipe de Kuzzle pour la révision du code et la relecture de cet article.

À la prochaine fois !

 

Nicolas Juelle

Postes associés