Gérer le mode hors-ligne grâce aux service workers

Corinne Schillinger - KIWIPARTY 2018

Les service workers sont-ils utilisables en production ?

À l'exception d'IE et d'Opera Mini les service workers sont supportés par les dernières versions de tous les navigateurs

Qu'est-ce qu'un service worker ?

  • prend la forme d’un fichier Javascript
  • est associé à un domaine
  • intercepte les requêtes serveur
  • permet de modifier les réponses retournées

fonctionnement standard

Le navigateur envoit la requête au serveur et attends sa réponse

fonctionnement avec un service worker

Le navigateur consulte le service worker pour connaître ses instructions et envoit la requête au serveur si nécessaire

Comment fonctionne un service worker ?

  • est téléchargé et exécuté en arrière-plan, parallèlement au contexte de la page
  • n’a pas accès au DOM
  • est non bloquant

Quelles sont les contraintes à respecter ?

  • nécessite l'utilisation du protocole HTTPS
    une seule exception : http://localhost
  • peut uniquement contrôler un site de même origine que lui (same-origin policy)
    = protocole, port et hôte identiques

Cas pratique

  1. enregistrer un service worker
  2. créer un cache grâce à l’interface Cache
  3. enregistrer les ressources nécessaires à l’affichage de la page en mode déconnecté
  4. intercepter les requêtes pour :
    • récupérer les ressources dans le cache
    • ou sur le serveur, si elles ne s’y trouvent pas

Enregistrer un service worker

  1. créer un fichier sw.js et le placer à la racine du site
  2. indiquer aux navigateurs l’existence du fichier
    navigator.serviceWorker.register('/sw.js');
  3. conditionner l’enregistrement au support du navigateur
    if (navigator.serviceWorker) {
      navigator.serviceWorker.register('/sw.js');
    }

Contrôler l’enregistrement

  1. utiliser les promesses JS pour savoir si l’enregistrement a réussi .then() ou échoué .catch()
    if (navigator.serviceWorker) {
      navigator.serviceWorker.register('/sw.js')
      .then(function() {
        // Succès de l’enregistrement
      })
      .catch(function() {
        // Échec de l’enregistrement
      });
    }
  2. afficher la portée du service worker ou la raison de l'échec de son enregistrement
    if (navigator.serviceWorker) {
      navigator.serviceWorker.register('/sw.js')
      .then(function(enregistrement) {
        console.log('Service Worker enregistré avec succès ! \nSa portée est :', enregistrement.scope);
      })
      .catch(function(erreur) {
        console.log('Échec de l\'enregistrement du Service Worker. \n', erreur);
      });
    }

Le cycle de vie d’un service worker

installation initiale

  1. enregistrement
  2. installation
  3. activation

mise à jour

  1. enregistrement
  2. installation
  3. attente
  4. activation

Créer un cache

  1. créer le cache grâce à la méthode open() sur l’objet caches
    caches.open('kiwi:001');
  2. suivre la création du cache grâce aux promesses
    caches.open('kiwi:001')
    .then(function(cache) {
      // Cache ouvert
    })
    .catch(function() {
      // Échec de l’ouverture du cache
    });

Créer un cache

  1. créer le cache à l’installation du service worker
    addEventListener('install', function() {
      caches.open('kiwi:001')
      .then(function() {
        // Remplissage du cache
      });
    });

Créer un cache

  1. conditionner l’installation du service worker à la création et au remplissage du cache
    addEventListener('install', function(installation) {
      installation.waitUntil(
        caches.open('kiwi:001')
        .then(function() {
          // Remplissage du cache
        });
      );
    });
  2. suivre la création et l'ouverture du cache
    addEventListener('install', function(installation) {
      console.log('Installation du SW en cours…');
      installation.waitUntil(
        caches.open('kiwi:001')
        .then(function() {
          console.log('Cache ouvert');
          // Remplissage du cache
        });
      );
    });

Enregistrer les fichiers dans le cache

  1. lister les URLs des ressources dans un tableau
    var fichiers = [
    	'index.html',
    	'kiwiparty_fichiers/styles.css',
    	'kiwiparty_fichiers/speaker-after.jpg',
    	'kiwiparty_fichiers/speaker-goutay.jpg',
    	'kiwiparty_fichiers/speaker-quiz.jpg',
    	'kiwiparty_fichiers/logo-wdstr.png',
    	'kiwiparty_fichiers/map-acces.png',
    	'kiwiparty_fichiers/speaker-arnaud-malon.png',
    	'kiwiparty_fichiers/speaker-aurelie.png',
    	…
    ];

Enregistrer les fichiers dans le cache

  1. passer le tableau d’URLs à la méthode addAll()
    cache.addAll(fichiers);
  2. retourner le résultat à l’événement install
    addEventListener('install', function(installation) {
      installation.waitUntil(
        caches.open('kiwi:001').then(function(cache) {
          return cache.addAll(fichiers);
        })
      );
    });

Intercepter les requêtes

  1. s’appuyer sur l’événement fetch
    addEventListener('fetch', function(ressource) {
      // Requête interceptée
    });

et c’est tout…

Récupérer la ressource dans le cache ou sur le serveur

  1. regarder si la ressource se trouve dans le cache
    caches.match(ressource.request);
  2. vérifier que la réponse n’est pas vide
    caches.match(ressource.request)
    .then(function(reponse) {
      if (reponse) {
        // Récupération de la ressource dans le cache
      } else {
        // Récupération de la ressource sur le serveur
      }
    });

Récupérer la ressource dans le cache ou sur le serveur

  1. récupérer la ressource depuis le cache ou le serveur
    caches.match(ressource.request)
    .then(function(reponse) {
      if (reponse) {
        return reponse;
      } else {
        return fetch(ressource.request);
      }
    });
  2. supprimer la condition inutile
    caches.match(ressource.request)
    .then(function(reponse) {
      if (reponse) {
        return reponse;
      }
      return fetch(ressource.request);
    });

Et enfin, retourner la ressource au navigateur

  1. retourner la ressource grâce à respondWith()
    ressource.respondWith(
      caches.match(ressource.request)
      .then(function(reponse) {
        if (reponse) {
          return reponse;
        }
        return fetch(ressource.request);
      })
    );
  2. … à chaque nouvelle requête
    addEventListener('fetch', function(ressource) {
      ressource.respondWith(
        caches.match(ressource.request)
        .then(function(reponse) {
          if (reponse) {
            return reponse;
          }
          return fetch(ressource.request);
        })
      );
    });

Pour aller plus loin

Merci ! des questions ?