Du Web hors-ligne et plus si affinités avec les Service Workers

Hubert SablonnièreHubert Sablonnière hsablonniere.com

Jurassic Park roarJurassic Park roar
Ron Weasley
Moi en 1993, enfin presque…​
Marchand de journaux
Le bon vieux “Marchand de journaux”
Web is so sexy
Séances de Jurassic World
VO ou VF?

Web > Marchand de journaux

Vraiment ?

Deuxième ciné dans la même semaine

Page Réseau HTTP Radio + DNS + TCP + TLS +
Marchand de journaux
Smartwatch
Nexus 5 speed test
D'avion vs d'internet complet
Unstable WiFi/4G
N’ayez confiance en personne.
Nexus 5 quota
Open space Salle de pause Micro-ondes
Bar debate

Presqu’Internet

Un forfait presque™ tout le temps rapide.

Fluctuating between Edge and 3G
page Web qui charge
Chrome offline
Firefox offlineFirefox offline

Offline-first

Offline first
offlinefirst.org

La connexion est une fonctionnalité

Soulager l'utilisateur sa batterie son forfait de données vos serveurs

Pocket
Allociné sans connexion
Allociné (sans connexion)
Feedly sans connexion
Feedly (sans connexion)
Twitter sans connexion
Twitter (sans connexion)

Application Cache

Support AppCache - Safari 4, IE 10, Firefox 3.5, Opera 11.5, Chrome 4
Support AppCache
<!-- index.html -->
<html manifest="/manifest.appcache">
<!-- ... -->
# manifest.appcache

CACHE MANIFEST
# version 1

CACHE:
index.html
page-1.html
page-2.html
index.css
index.js
mon-logo.jpg

NETWORK:
*

FALLBACK:
list.html no-connexion.html
AppCache : premier chargement
AppCache : premier chargement
AppCache : autre chargements
AppCache : autres chargements
AppCache : vérification du manifest
AppCache : vérification du manifest
# manifest.appcache

CACHE MANIFEST
# version 2 (1)

CACHE:
index.html
page-1.html
page-2.html
index-22db3627.css (2)
index.js
mon-logo.jpg
1 incrémentation de la version
2 OU ajout d’un hash
Page Réseau App Cache 1 2 manifest v2 ? 3 oui ! 4 index.html maven.js main.css ...
On rest sur la version d'avant
On reste sur la version d’avant
var appCache = window.applicationCache;

appCache.addEventListener('updateready', function (event) {

    if (confirm('Voulez-vous la nouvelle version ?')) {
        window.location.reload();
    }
});
Tuco
The Good & the Ugly
The Ugly sticking up the Good
Application Cache is a Douchebag article
devdocs.io
devdocs.io

Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson.

Confucius
Fishing rod

Service Workers

Service Worker spec
http://www.w3.org/TR/service-workers

Proxy programmable dans le navigateur entre vos pages et votre serveur qui a son propre cache

Navigateur Pages Réseau Service Worker Service Worker Cache

HTTPS https://sousdomaine.domaine:port

Let's encrypt
https://letsencrypt.org
// page.js

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
      .then(function (registration) {
          console.log('SW OK ;->', registration.scope);
      })
      .catch(function (err) {
          console.log('SW KO :-<', err);
      });
}

Promises JavaScript

// sw.js (thread indépendant de la page)




​
// sw.js (pas d'objet global window)

console.log(typeof window === 'undefined'); // true


​
// sw.js

self.addEventListener('fetch', function (event) {});


​
// sw.js

self.addEventListener('fetch', function (event) {
    console.log('request', event.request.url);
});
​
// sw.js (réponse générée)

self.addEventListener('fetch', function (event) {
    console.log('request', event.request.url);
    event.respondWith(new Response('Hello World!'));
});

Nouvelle API de stockage le cache

// sw.js (réponse depuis le cache)

self.addEventListener('fetch', function (event) {





        event.respondWith(caches.match(event.request));

});
// sw.js (réponse depuis serveur ou depuis le cache)

self.addEventListener('fetch', function (event) {

    if (event.request.url.match('/api') {
        event.respondWith(/* avec le serveur */);
    }
    else {
        event.respondWith(caches.match(event.request));
    }
});

XMLHttpRequest

// page.js

var url = '/api/conferences';

var xhr = new XMLHttpRequest();
var async = false; (1)
xhr.open('GET', url, async);
xhr.responseType = 'json';
xhr.onload = function () {
    var conferences = xhr.response;
    console.log('confs', conferences);
};
xhr.onerror = function () {
    console.log('error');
};
xhr.send();
1 appel synchrone bloquant :-(

Nouvelle API de requête fetch()

Fetch spec
http://fetch.spec.whatwg.org
// page.js ou sw.js

var url = '/api/conferences';

fetch(url) (1)
  .then(function (response) {
      return response.json();
  })
  .then(function (conferences) {
      console.log('confs', conferences);
  })
  .catch(function (error) {
      console.log('error', error);
  });
1 string ou objet Request
// page.js ou sw.js

var url = '/api/conferences';

fetch(url, { method: 'POST' }) (1)
  .then(function (response) {
      return response.json();
  })
  .then(function (conferences) {
      console.log('confs', conferences);
  })
  .catch(function (error) {
      console.log('error', error);
  });
1 config de la requête
// sw.js (réponse depuis serveur ou depuis le cache)

self.addEventListener('fetch', function (event) {

    if (event.request.url.match('/api') {
        event.respondWith(fetch(event.request));
    }
    else {
        event.respondWith(caches.match(event.request));
    }
});

Cycle de vie d’un Service Worker

// page.js

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(onSuccess)
        .catch(onError);
}
// sw.js









self.addEventListener('install', function (event) {
    // le service worker s'installe




});



​
// sw.js

var CURRENT_CACHE = 'mon-site-v1',
    urlsToCache = [
        '/',
        '/main.css',
        '/index.js',
        '/logo.jpg'
    ];

self.addEventListener('install', function (event) {
    // le service worker s'installe




});



​
// sw.js

var CURRENT_CACHE = 'mon-site-v1',
    urlsToCache = [
        '/',
        '/main.css',
        '/index.js',
        '/logo.jpg'
    ];

self.addEventListener('install', function (event) {
    event.waitUntil(
        // attend avant qu'une promise se résolve


    );
});



​
// sw.js

var CURRENT_CACHE = 'mon-site-v1',
    urlsToCache = [
        '/',
        '/main.css',
        '/index.js',
        '/logo.jpg'
    ];

self.addEventListener('install', function (event) {
    event.waitUntil(
        caches.open(CURRENT_CACHE)


    );
});



​
// sw.js

var CURRENT_CACHE = 'mon-site-v1',
    urlsToCache = [
        '/',
        '/main.css',
        '/index.js',
        '/logo.jpg'
    ];

self.addEventListener('install', function (event) {
    event.waitUntil(
        caches.open(CURRENT_CACHE).then(function (cache) {
            return cache.addAll(urlsToCache);
        })
    );
});



​
// sw.js

var CURRENT_CACHE = 'mon-site-v1',
    urlsToCache = [
        '/',
        '/main.css',
        '/index.js',
        '/logo.jpg'
    ];

self.addEventListener('install', function (event) {
    event.waitUntil(
        caches.open(CURRENT_CACHE).then(function (cache) {
            return cache.addAll(urlsToCache);
        })
    );
});

self.addEventListener('activate', function (event) {
    // le SW essaye de s'activer
});
Service Worker EVENT install Ajouts pages ouvertescontrôléespar ancien SW? OK EVENT activate Ménage
Cycle de vie d'un Service Worker
Le nouveau SW prend le contrôle
// page.js

navigator.serviceWorker.register('/sw.js')
    .then(function (registration) {

        // un objet avec ce que je veux dedans
        registration.active.postMessage({
            command: 'forceUpdate',
            foobar: 42
        });
    });
// sw.js

self.addEventListener('message', function (event) {

    if (event.data.command === 'forceUpdate') {
        // mettre à jour le cache kivabien avec le serveur
    }
});
// sw.js

self.addEventListener('fetch', function (event) {

    if (event.request.url.match('/logout') {

        // prevenir tous les onglets de la déconnexion







    }
});
// sw.js

self.addEventListener('fetch', function (event) {

    if (event.request.url.match('/logout') {

        clients.matchAll().then(function (windowClients) {






        });
    }
});
// sw.js

self.addEventListener('fetch', function (event) {

    if (event.request.url.match('/logout') {

        clients.matchAll().then(function (windowClients) {

            windowClients.forEach(function (client) {
                client.postMessage({
                    command: 'logout'
                });
            });
        });
    }
});
// page.js

var sw = navigator.serviceWorker;
sw.addEventListener('message', function (event) {

    if (event.data.command === 'logout') {

        // rediriger vers l'accueil ou le login
        // vider le localStorage
        // cacher l'avatar
        // ...
    }
});

Debug ? Devtools ?

Next Speaker
https://nextspeaker.io
About Service Workers
about:serviceworkers
Chome Service Worker internals
chrome://serviceworker-internals
Chrome DevToolsChrome DevToolsChrome DevToolsChrome DevToolsChrome DevToolsChrome DevToolsChrome DevToolsChrome DevTools
Chrome DevTools debug
Chrome DevTools debug

C’est pour qui ?

Single Page Application

Next Speaker

Server side rendering

Jurassic Park sur Wikipedia
Wikipedia Offline
https://wiki-offline.jakearchibald.com

Service Worker side rendering

CSS pour les nulsCSS pour les nuls
BPG2JPG polyfill demo

La bonne recette

Offline cookbook

Ça marche où ?

Support Service Worker - Chrome & Opera (stable) < 100%, Firefox (nightly) < 100%, IE under consideration, Safari ???
Is Service Worker ready?

Pas de polyfill mais les helpers arrivent

sw-toolbox project
serviceworkerware project
sw-precache project
UpUp project
Escalator

Natif vs. Web

Next Speaker mobile desktop icon
Next Speaker iPhone
Next Speaker mobile site
Ajouter à l'écran d'accueil Chrome
Ajouter à l'écran d'accueil Firefox
Next Speaker mobile desktop icon
Next Speaker mobile full screen

Manifest for a web application

Manifest spec
http://www.w3.org/TR/appmanifest
// manifest.json
{
    "short_name": "Next Speaker",
    "name": "Next Speaker, programme des conférences",
    "icons": [
        {
            "src": "launcher/next-speaker-2x.png",
            "sizes": "96x96",
            "type": "image/png",
            "density": "2.0"
        },
        /* ... */
    ],
    "start_url": "index.html",
    "display": "standalone", (1)
    "orientation": "portrait",
    "background_color": "#F00F00"
    "theme_color": "#3080BB"
}
1 pas de barres navigateur
Ajouter à l'écran bouton
Ajouter à l'écran bouton

La suite ...push & notifications ........streaming des résponses ........................synchronisation ...................................geofencing ...............................................................( alarmes )

Firefox offline

Merci ;-)

le “super” public ...................................................... Vous
l'anecdote des toilettes ........................................................... Jake Archibald
les idées pour les schémas ......................................................... Jake Archibald
les articles intéressants .......................................................... Jake Archibald
les articles intéressants .......................................................... Ben Kelly
les articles intéressants .......................................................... Matt Gaunt
les talks qui m'ont inspirés ....................................................... Jake Archibald
les talks qui m'ont inspirés ....................................................... Alex Russel
les icônes des schémas ............................................................. TheNounProject

Avez vous des Questions ?