Données hors connexion

Pour créer une expérience hors connexion solide, votre PWA doit être gérée. Dans le chapitre consacré à la mise en cache, vous avez appris que le stockage en cache est l'une des options permettant d'enregistrer des données sur un appareil. Dans ce chapitre, nous allons vous expliquer comment gérer les données hors connexion, y compris la persistance des données, les limites et les outils disponibles.

Le stockage ne se résume pas aux fichiers et aux éléments, il peut inclure d'autres types de données. Sur tous les navigateurs compatibles avec les PWA, les API suivantes sont disponibles pour le stockage sur l'appareil:

  • IndexedDB: option de stockage d'objets NoSQL pour les données structurées et les blobs (données binaires).
  • WebStorage: un moyen de stocker des paires de chaînes clé/valeur à l'aide d'un stockage local ou de session. Elle n'est pas disponible dans le contexte d'un service worker. Cette API étant synchrone, elle n'est pas recommandée pour le stockage de données complexe.
  • Stockage du cache: comme expliqué dans le module de mise en cache.

L'API Storage Manager vous permet de gérer tout l'espace de stockage de l'appareil sur les plates-formes compatibles. L'API Cache Storage et IndexedDB fournissent un accès asynchrone au stockage persistant pour les PWA. Ils sont accessibles depuis le thread principal, les workers Web et les service workers. Les deux jouent un rôle essentiel dans le fonctionnement fiable des PWA lorsque le réseau est instable ou inexistant. Mais quand les utiliser ?

Utilisez l'API Cache Storage pour les ressources réseau, c'est-à-dire les éléments auxquels vous auriez accès en les demandant via une URL, comme du code HTML, CSS, JavaScript, des images, des vidéos et du contenu audio.

Utilisez IndexedDB pour stocker des données structurées. Cela inclut les données qui doivent être incluses dans l'index de recherche ou pouvant être combinées de manière NoSQL, ou d'autres données telles que les données spécifiques à un utilisateur qui ne correspondent pas nécessairement à une requête d'URL. Notez que IndexedDB n'est pas conçu pour la recherche en texte intégral.

IndexedDB

Pour utiliser IndexedDB, commencez par ouvrir une base de données. Une base de données est créée s'il n'en existe aucune. La base de données IndexedDB est une API asynchrone, mais elle utilise un rappel au lieu de renvoyer une promesse. L'exemple suivant utilise la bibliothèque idb de Jake Archibld, qui est un minuscule wrapper Promise pour IndexedDB. Les bibliothèques d'aide ne sont pas obligatoires pour utiliser IndexedDB, mais si vous souhaitez utiliser la syntaxe Promise, vous pouvez utiliser la bibliothèque idb.

L'exemple suivant crée une base de données pour stocker des recettes de cuisine.

Créer et ouvrir une base de données

Pour ouvrir une base de données:

  1. Utilisez la fonction openDB pour créer une base de données IndexedDB appelée cookbook. Les bases de données IndexedDB étant gérées par version, vous devez augmenter le numéro de version chaque fois que vous modifiez la structure de la base de données. Le deuxième paramètre correspond à la version de la base de données. Dans cet exemple, est défini sur 1.
  2. Un objet d'initialisation contenant un rappel upgrade() est transmis à openDB(). La fonction de rappel est appelée lors de la première installation de la base de données ou lors d'une mise à niveau vers une nouvelle version. Cette fonction est le seul endroit où des actions peuvent se produire. Les actions peuvent inclure la création de nouveaux magasins d'objets (les structures utilisées par IndexedDB pour organiser les données) ou d'index (dans lesquels vous souhaitez effectuer une recherche). C'est également là que la migration des données doit se faire. En règle générale, la fonction upgrade() contient une instruction switch sans instructions break pour permettre à chaque étape de se dérouler dans l'ordre, en fonction de l'ancienne version de la base de données.
import { openDB } from 'idb';

async function createDB() {
  // Using https://github.com/jakearchibald/idb
  const db = await openDB('cookbook', 1, {
    upgrade(db, oldVersion, newVersion, transaction) {
      // Switch over the oldVersion, *without breaks*, to allow the database to be incrementally upgraded.
    switch(oldVersion) {
     case 0:
       // Placeholder to execute when database is created (oldVersion is 0)
     case 1:
       // Create a store of objects
       const store = db.createObjectStore('recipes', {
         // The `id` property of the object will be the key, and be incremented automatically
           autoIncrement: true,
           keyPath: 'id'
       });
       // Create an index called `name` based on the `type` property of objects in the store
       store.createIndex('type', 'type');
     }
   }
  });
}

L'exemple crée un magasin d'objets dans la base de données cookbook appelé recipes, avec la propriété id définie comme clé d'index du magasin et crée un autre index appelé type, basé sur la propriété type.

Jetons un coup d'œil au magasin d'objets qui vient d'être créé. Après avoir ajouté des recettes au magasin d'objets et ouvert les outils de développement dans les navigateurs basés sur Chromium ou l'inspecteur Web dans Safari, vous devriez obtenir le résultat suivant:

Safari et Chrome affichant le contenu IndexedDB.

Ajouter des données

IndexedDB utilise des transactions. Les transactions regroupent les actions afin qu'elles s'effectuent comme une unité. Elles permettent de garantir que la base de données est toujours dans un état cohérent. Elles sont également essentielles si plusieurs copies de votre application sont en cours d'exécution, afin d'éviter toute écriture simultanée sur les mêmes données. Pour ajouter des données:

  1. Lancez une transaction avec mode défini sur readwrite.
  2. Récupérez le magasin d'objets, dans lequel vous ajouterez des données.
  3. Appelez add() avec les données que vous enregistrez. La méthode reçoit les données sous forme de dictionnaire (sous forme de paires clé/valeur) et les ajoute au magasin d'objets. Le dictionnaire doit pouvoir être cloné à l'aide du clonage structuré. Si vous souhaitez mettre à jour un objet existant, appelez plutôt la méthode put().

Les transactions ont une promesse done qui se résout lorsque la transaction aboutit, ou qui sont rejetées avec une erreur de transaction.

Comme l'explique la documentation de la bibliothèque IDB, si vous écrivez dans la base de données, tx.done indique que tout a bien été validé dans la base de données. Toutefois, il est préférable d'attendre les opérations individuelles afin d'afficher les erreurs qui entraînent l'échec de la transaction.

// Using https://github.com/jakearchibald/idb
async function addData() {
  const cookies = {
      name: "Chocolate chips cookies",
      type: "dessert",
        cook_time_minutes: 25
  };
  const tx = await db.transaction('recipes', 'readwrite');
  const store = tx.objectStore('recipes');
  store.add(cookies);
  await tx.done;
}

Une fois que vous aurez ajouté les cookies, la recette figurera dans la base de données avec d'autres recettes. L'ID est automatiquement défini et incrémenté par "indexedDB". Si vous exécutez ce code deux fois, vous obtiendrez deux entrées de cookie identiques.

Récupération des données…

Voici comment obtenir des données à partir de IndexedDB:

  1. Démarrez une transaction et spécifiez le ou les magasins d'objets, ainsi que le type de transaction (facultatif).
  2. Appelez objectStore() à partir de cette transaction. Veillez à spécifier le nom du magasin d'objets.
  3. Appelez get() avec la clé que vous souhaitez obtenir. Par défaut, le store utilise sa clé comme index.
// Using https://github.com/jakearchibald/idb
async function getData() {
  const tx = await db.transaction('recipes', 'readonly')
  const store = tx.objectStore('recipes');
// Because in our case the `id` is the key, we would
// have to know in advance the value of the id to
// retrieve the record
  const value = await store.get([id]);
}

Le gestionnaire d'espace de stockage

Il est particulièrement important de savoir gérer le stockage de votre PWA pour stocker et diffuser correctement les réponses du réseau.

La capacité de stockage est partagée entre toutes les options de stockage, y compris le stockage du cache, IndexedDB, le stockage Web, et même le fichier service worker et ses dépendances. Cependant, l'espace de stockage disponible varie d'un navigateur à l'autre. Vous ne risquez pas d'en manquer. les sites pouvaient stocker des mégaoctets, voire des gigaoctets de données sur certains navigateurs. Chrome, par exemple, permet au navigateur d'utiliser jusqu'à 80% de l'espace disque total, tandis qu'une origine individuelle peut utiliser jusqu'à 60% de l'espace disque total. Pour les navigateurs compatibles avec l'API Storage, vous pouvez connaître la quantité d'espace de stockage encore disponible pour votre application, son quota et l'utilisation qui en est faite. L'exemple suivant utilise l'API Storage pour obtenir une estimation du quota et de l'utilisation, puis calcule le pourcentage utilisé et les octets restants. Notez que navigator.storage renvoie une instance de StorageManager. Il existe une interface Storage distincte et il est facile de les confondre.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

Dans les outils pour les développeurs Chromium, vous pouvez consulter le quota de votre site et la quantité d'espace de stockage utilisée, pour chaque utilisateur. Pour ce faire, ouvrez la section Stockage de l'onglet Application.

Outils pour les développeurs Chrome dans "Application", section "Vider l'espace de stockage"

Firefox et Safari n'offrent pas d'écran récapitulatif permettant de voir l'ensemble des quotas de stockage et de l'utilisation pour l'origine actuelle.

Persistance des données

Vous pouvez demander au navigateur un stockage persistant sur les plates-formes compatibles pour éviter l'éviction automatique des données après une inactivité ou en cas de pression sur le stockage. Si cette autorisation est accordée, le navigateur n'évince jamais les données de l'espace de stockage. Cette protection inclut l'enregistrement du service worker, les bases de données IndexedDB et les fichiers stockés dans le cache. Notez que les utilisateurs sont toujours aux commandes et qu'ils peuvent supprimer l'espace de stockage à tout moment, même si le navigateur a activé le stockage persistant.

Pour demander un stockage persistant, appelez StorageManager.persist(). Comme précédemment, l'interface StorageManager est accessible via la propriété navigator.storage.

async function persistData() {
  if (navigator.storage && navigator.storage.persist) {
    const result = await navigator.storage.persist();
    console.log(`Data persisted: ${result}`);
}

Vous pouvez également vérifier si le stockage persistant est déjà accordé dans l'origine actuelle en appelant StorageManager.persisted(). Firefox demande à l'utilisateur l'autorisation d'utiliser le stockage persistant. Les navigateurs basés sur Chromium assurent ou refusent la persistance en se basant sur une heuristique permettant de déterminer l'importance du contenu pour l'utilisateur. L'un des critères de Google Chrome est, par exemple, l'installation d'une PWA. Si l'utilisateur a installé une icône pour la PWA dans le système d'exploitation, le navigateur peut accorder un stockage persistant.

Mozilla Firefox demandant à l'utilisateur une autorisation de persistance du stockage

Compatibilité avec les navigateurs d'API

Stockage Web

Navigateurs pris en charge

  • Chrome: 4. <ph type="x-smartling-placeholder">
  • Edge: 12 <ph type="x-smartling-placeholder">
  • Firefox: 3.5. <ph type="x-smartling-placeholder">
  • Safari: 4. <ph type="x-smartling-placeholder">

Source

Accès au système de fichiers

Navigateurs pris en charge

  • Chrome: 86 <ph type="x-smartling-placeholder">
  • Edge: 86 <ph type="x-smartling-placeholder">
  • Firefox: 111 <ph type="x-smartling-placeholder">
  • Safari: 15.2. <ph type="x-smartling-placeholder">

Source

Gestionnaire d'espace de stockage

Navigateurs pris en charge

  • Chrome: 55 <ph type="x-smartling-placeholder">
  • Edge: 79 <ph type="x-smartling-placeholder">
  • Firefox: 57 <ph type="x-smartling-placeholder">
  • Safari: 15.2. <ph type="x-smartling-placeholder">

Source

Ressources