README.md 16.7 KB
Newer Older
1 2 3 4 5 6
# Offline

Plugin pour SPIP 3.1+ permettant de proposer simplement la consultation offline d'un site SPIP existant

## Principe

Cerdic's avatar
Cerdic committed
7 8 9 10
Le plugin Offline s'appuie sur les ServiceWorkers pour permettre la mise en cache des pages du site dans le navigateur et réutiliser ce cache quand la connexion internet est coupée.

Le ServiceWorker est un morceau de code autonome qu'on envoie dans le navigateur de l'utilisateur et qui va ensuite fonctionner indépendamment en interceptant les requêtes pour le site concerné et en gérant la mise dans un cache pour plus tard si on est connecté, ou en utilisant le fichier dans le cache si on est offline.

11 12 13
## Mode d'emploi

Installer le plugin
14 15 16 17 18 19 20 21 22

Ajouter en debut de section du fichier `.htaccess` les RewriteRules suivantes

```
RewriteCond %{REQUEST_URI} offline\.api\.(.+)$
RewriteCond local/offline/%1 -f
RewriteRule ^offline\.api\.(.+)$ local/offline/$1 [QSA,L,skip=100]
```

23 24 25 26
Modifier dans le .htaccess la RewriteRule des api pour permettre d'appeler l'action API avec une URL de la forme `/offline.api.sw.js`
```
RewriteRule ^([\w]+)\.api([/.](.*))?$ spip.php?action=api_$1&arg=$3 [QSA,L]
```
27

28 29 30 31
De cette manière, l'appel à l'URL `/offline.api.sw.js` servira
* le fichier statique `local/offline/sw.js` si il existe
* l'url `?action=api_offline&arg=sw.js` sinon, qui se chargera de le reconstruire

32 33
### Configuration

34 35 36 37 38 39
La constante `_OFFLINE_DEBUG` permet de faire fonctionner le plugin avec des logs verbeux dans la console JS et sans caching du service-worker côté serveur.
Commencez par l'ajouter à votre fichier `mes_options.php` pour tester le plugin (mais il faudra penser à l'enlever avant toute mise en production)
```
define('_OFFLINE_DEBUG', true);
```

Cerdic's avatar
Cerdic committed
40 41 42 43 44 45 46 47 48 49 50
Se rendre sur la page de configuration du plugin `ecrire/?exec=configurer_offline` pour configurer et activer le mode offline.

* Version du cache : ce numéro de version est pris en compte dans le nom du cache utilisé côté navigateur. Il vous permet par exemple de forcer une mise à jour des pages mises en cache chez les utilisateurs lorsque vous venez de publier une nouvelle version du site.
* Activation du mode offline : choix du mode d'activation (cf le principe de fonctionnement ci-dessous)
* Stratégie pour les pages : choix de la stratégie de service des pages du site
  * vous pouvez privilégier le réseau pour être sur d'avoir la dernière version, mais en connexion mobile intermittente l'utilisateur devra attendre à chaque fois que le navigateur se rende compte que non, la connexion ne passe pas, avant d'avoir la page en cache
  * vous pouvez privilégier le contenu déjà en cache. Dans ce cas il est chaque fois remis à jour depuis le réseau si possible pour que la prochaine fois le contenu soit à jour
* Stratégie pour les ressources : ici cela concerne la stratégie pour toutes les images, css, js… associées à une page.
  * si vous privilégiez le réseau, idem que pour les pages, on essaye d'abord avec la connexion
  * si vous privilégiez le cache vous aurez un fonctionnement plus rapide. Ici pour toutes les ressources statiques connues de SPIP et les ressources timestampées on s'épargne une mise à jour en background, ce qui économise en plus des requêtes réseau
* Taille maxi (ko) des medias mises en cache : vous pouvez limiter la taille maximum des medias (image, audio, video) mis en cache pour éviter de remplir le cache de l'utilisateur. Si vous mettez 0 il n'y aura pas de limite (déconseillé)
Cerdic's avatar
Cerdic committed
51 52
  L'image de fallback `plugins/offline/img/fallback.png` utilisée à la place des images qui ne sont pas en cache quand l'utilisateur est hors-ligne est surchargeable dans votre dossier `squelettes/`<br />
  Si vous utilisez une limite de taille vous pouvez toujours forcer la mise en cache offline d'un media en ajoutant un `?offline-cache` dans son URL
Cerdic's avatar
Cerdic committed
53
* URL 404 offline : c'est l'URL de la page qu'on présentera aux utilisateurs quand ils demandent une URL qui n'est pas en cache et qu'on n'a pas de réponse du réseau. Par défaut le plugin propose une page `404_offline` construite à partir de la page 404 de votre site, mais vous pouvez la personaliser ou choisir une autre URL.
Cerdic's avatar
Cerdic committed
54 55
* Ressources supplémentaires à mettre en cache : Par défaut la home page et l'URL 404 offline du site ainsi que leurs ressources seront mise en cache lors de l'activation.<br />
  Si certaines ressources spécifiques manquaient ou si vous voulez mettre en cache d'autres pages par défaut, indiquez les URLs ici
Cerdic's avatar
Cerdic committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
  


### Principe de fonctionnement

Le plugin Offline s'appuie sur les ServiceWorkers pour permettre la mise en cache des pages du site dans le navigateur et réutiliser ce cache quand la connexion internet est coupée.

Il faut comprendre que le ServiceWorker est un morceau de code autonome qu'on envoie dans le navigateur de l'utilisateur et qui va ensuite fonctionner indépendamment.
Si on modifie le code ou la version du cache, l'utilisateur ne verra l'effet de ces modifications que lorsqu'il sera venu sur une page du site qui envoie une nouvelle mise à jour, et qu'il aura fermé puis réouvert son navigateur sur le site.

Ainsi si on arrête simplement de proposer la fonctionnalité sur le site, tous les utilisateurs vont continuer à fonctionner avec la dernière version du ServiceWorker qu'ils auront téléchargé.

Si donc on propose le mode offline aux utilisateurs connectés, ils auront l'installation de la fonction en étant connecté, puis
les mises à jour de la fonction chaque fois qu'ils seront connectés. 
Mais quand ils seront déconnectés ils continueront à bénéficier de la fonctionnalité, simplement sans mise à jour du code ou sans prise en compte des nouvelles versions du cache.

De même, pour désactiver la fonctionnalité hors-ligne chez les visiteurs, il ne faut surtout pas désactiver le plugin !
Cerdic's avatar
Cerdic committed
73 74
Au lieu de cela il faut choisir le mode "Désinstaller le mode offline chez les visiteurs " et le laisser fonctionner ainsi — alternativement il faut récuperer le code de désinstallation, le mettre en dur dans le site et il est ensuite possible de désactiver le plugin
(cf https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#updates)
75

Cerdic's avatar
Cerdic committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
#### URLs des services

Les scripts sont servis via les URLs suivantes :
* `/offline.api.install.js` : script d'installation du service, qui est automatiquement intégré dans le `<head>` des pages publiques si vous choisissez l'activation automatique
* `/offline.api.sw.js` : le service-worker en lui même, avec la configuration et ses fonctions utiles
* `/offline.api.uninstall.js` : le script de desinstallation du service, , qui est automatiquement intégré dans le `<head>` des pages publiques si vous choisissez la désactivation

Dans le navigateur l'espace d'execution du service worker est indépendant de celui de la page.
Ainsi une fonction JS disponible dans la page (et dans la console) ne sera pas disponible pour le service worker. De même les fonctions du service worker sont non accessibles depuis les pages web du site

En mode debug, ces URLs sont servies par `action/api_offline.php` de manière dynamique.
En production le plugin produit des fichiers statiques minifiés dans `local/offline/` qui sont servis de manière statique par Apache via la modification dans votre `.htaccess`
Si vous voulez repasser en mode debug après avoir testé le mode production, il faut supprimer les fichiers statiques pour permettre au script PHP de reprendre la main.

#### Build

Le service worker est uploadé avec une liste d'URL à télécharger pour rendre disponibles certaines pages hors-ligne - a minima la home du site et la page d'erreur 404 offline.
Pour rendre ces pages hors ligne, il faut construire la liste des URLs utilisées par ces pages.
Ce mécanisme nécessite de télécharger chaque page, puis de les parcourir pour détecter les ressources utiles, et de vérifier leur validité
(si une URL invalide ou en 404 est contenue dans la demande de mise en cache, aucune URL du lot n'est mise en cache à l'installation)

Ce build peut nécessiter du temps et n'est donc jamais fait de manière synchrone immédiate.
Cerdic's avatar
Cerdic committed
98
Il est déclenché en tache de fond lors de l'enregistrement d'une nouvelle configuration
Cerdic's avatar
Cerdic committed
99 100 101 102 103 104 105

Si votre site n'est pas fréquenté ou que vous voulez en disposer immédiatement, vous pouvez lancer la commande via spip-cli :

```
spip offline:build:services
```

Cerdic's avatar
Cerdic committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
#### Téléchargement d'une partie du site "pour lire plus tard"

Le plugin propose une balise `#BOUTON_TELECHARGER_OFFLINE`  qui peut s'utiliser ainsi :

```
<BOUCLE_titre(RUBRQUES){id_rubrique}>
<h1>#TITRE</h1>
[(#BOUTON_TELECHARGER_OFFLINE{rubrique,#ID_RUBRIQUE})]
</BOUCLE_titre>
```

Vous pouvez également passer 2 arguments optionnels supplémentaires correspondant au libellé du bouton de téléchargement actif et inactif

```
#BOUTON_TELECHARGER_OFFLINE{rubrique,#ID_RUBRIQUE,'Télécharger cette documentation','Téléchargement non disponible'}
```

Le bouton télécharger repose sur 2 squelettes :
* le squelette `offline/urls-{objet}.html` (`offline/urls-rubrique.html` pour une rubrique) qui liste les URLs des pages à télécharger pour l'objet passé en `#ENV` du squelette. La liste est au format texte, une URL par ligne.
* le squelette `offline/bouton-telecharger.html` qui gère l'affichage du bouton


La balise `#BOUTON_TELECHARGER_OFFLINE` se charge de vérifier l'existence d'un squelette `offline/urls-{objet}.html`. 
Si le squelette n'existe pas elle n'affichera rien.

La balise se charge également de vérifier qu'une liste d'URL a été construite pour cet objet, à partir de la liste des pages fournies par le squelette `offline/urls-{objet}.html`.
Si la liste des URLs n'a pas été construite, elle lance une tache cron pour construire cette liste et affiche le bouton dans un état `unactive`.
Pour des raisons de performance, le bouton n'est pas dynamique : il ne vérifie pas l'état de la liste à chaque affichage mais seulement à la mise à jour du cache.
Il faudra donc attendre la prochaine mise à jour du cache ou forcer avec un `?var_mode=calcul` pour qu'il sorte de cet état.

Quand la liste des URLs pour l'objet demandé est bien disponible, le bouton est affiché dans le HTML avec un état `unactive activable`.
C'est une fonction javascript qui va se charger, côté client, de vérifier que le service worker est bien chargé, et de passer alors le bouton dans l'état `active`.

Au même moment il vérifie aussi si la liste des URLs a déja été chargée pour cet objet, et dans ce cas la classe `offline-ok` est ajoutée.
Attention, cela ne prend pas en compte un éventuel changement de la liste des URLs suite à la mise à jour du contenu !

Enfin, quand l'utilisateur clique sur le bouton pour (re)télécharger le contenu, le bouton reçoit la classe `offline-download`

La construction de la liste des URLs de téléchargement d'un objet peut se faire automatiquement en cron, mais aussi manuellement, via spip-cli :
```
spip offline:build:urls --objet=rubrique --id_objet=1
```

Le build construit un fichier texte `config/offline/objets/urls-rubrique-1.txt` qui sera alors chargé au format json par le navigateur via l'URL `offline.api.urlsdownload.json?objet=rubrique&id_objet=1`
Cerdic's avatar
Cerdic committed
150 151 152 153 154 155 156


#### Utilisation sur un site multidomaines

La prise en compte de multidomaines n'est pas encore vraiment opérationnelle. 
Les URLs à télécharger doivent être relatives au domaine consulté, et les éventuelles URLs croisées seront bloquées en l'absence de directives CORS.

Cerdic's avatar
Cerdic committed
157 158
### Mise à jour des listes d'URLs

Cerdic's avatar
Cerdic committed
159
Pour assurer une mise à jour de toutes les listes d'URLs existantes, il est possible de lancer depuis un cron système la commande
Cerdic's avatar
Cerdic committed
160 161 162 163 164 165
```
spip offline:rebuild
```

qui va reconstruire le service worker en mettant à jour la liste d'URLs à charger à l'installation, mais également repasser sur chaque fichier `config/offline/objets/urls-{objet}-{id_objet}.txt` existant et recalculer la liste des URLs à charger pour cet objet.

Cerdic's avatar
Cerdic committed
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
## Considérations de performance

Pour optimiser le temps de chargement et revalidation des ressources (CSS, JS), il est préconisé de les minifier et concaténer en un seul fichier au sein de chaque page.
Le plugin Compresseur livré avec SPIP permet cela automatiquement.

Toutesfois dans le cadre de l'utilisation du plugin Offline et de la mise en cache utilisateur des ressources pour navigation hors-ligne cette pratique doit-être reconsidérée.

### Fonctionnel hors ligne
D'un point de vue fonctionnel, la concaténation en un seul fichier présente un inconvénient majeur : 
le nommage du fichier concaténé dépend de la liste des ressources qui y sont intégrées.

Ainsi, si une page particulière embarque une ressource supplémentaire, le fichier concaténé référencé ne sera pas le même que sur les autres pages, et la navigation hors ligne sur cette page sera brisée, ou a minima nécessite de mettre plus de ressources en cache.

Pire encore : pour garantir que le fichier concaténé se mets à jour quand une ressource intégrée change, celles-ci sont en général référencées avec un timestamp.
Résultat : si une ressource est éditée et (même légèrement) modifiée, le fichier concaténé référencé sur la page change complètement de nom.

A contrario, si on garde des ressources non concaténées dans les pages, la gestion du cache hors-ligne est simplifiée, car chaque ressource peut-être servie de façon atomique.
Qui plus est, le plugin gère les ressources timestampées intelligemment : la mise en cache se fait avec le timestamp, mais aussi sans le timestamp en tant que fallback generique.
Ainsi, si pour une page en cache venait à manquer une ressource timestampée, le plugin sera capable de servir une version presque équivalent à titre de fallback 
(sauf en cas de grosses modificiations des ressources côté serveur, mais dans ce cas il est préconisé d'incrémenter la version de cache éditoriale) 

### Performance : Offline + HTTP/2

L'utilisation du plugin Offline nécessite https pour des raisons de sécurité (les service-workers permettent de modifier profondément le fonctionnement ET le contenu du site, il est donc impératif qu'ils soient délivrés à l'utilisateur sans risque de corruption par une attaque de type MITM)
Du coup, si l'on combine https avec HTTP/2, alors il peut devenir globalement optimal de désactiver la concaténation des feuilles de style et des JS en un seul fichier, et de privilégier un service atomique des ressources.

Sur un hit à froid, hors utilisation du mode offline, et sans aucun cache navigateur, cette structure de page n'est plus que très peu pénalisante, car HTTP/2 compense en servant les ressources dans un pipeline, les unes à la suite des autres.
Et une fois que le mode offline est actif, on bénéficie alors des mises en cache atomiques des ressources, qui sont toujours servies depuis le cache dès lors qu'elles ont été téléchargées une fois
(le timestamp des URLs garantissant un nouveau téléchargement si la ressource change).
Une page contenant juste une ressource de plus chargera beaucoup plus vite.

A partir du moment où la typologie des visiteur du site est majoritaire constituée de visiteurs réguliers, et avec HTTP/2, cette structure de page est donc à privilégier (on veillera toutefois à conserver une minification individuelle des ressources).

199 200 201 202

# Biblio

## References
Cerdic's avatar
Cerdic committed
203 204 205 206
* https://jakearchibald.github.io/isserviceworkerready/
* https://jakearchibald.com/2014/offline-cookbook/
* https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/
* https://developers.google.com/web/fundamentals/primers/service-workers/
207

Cerdic's avatar
Cerdic committed
208
* https://serviceworke.rs/
Cerdic's avatar
Cerdic committed
209 210 211
* https://developer.mozilla.org/fr/docs/Web/API/Service_Worker_API
* https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
* https://www.w3.org/TR/service-workers-1/
Cerdic's avatar
Cerdic committed
212 213 214 215
* https://fetch.spec.whatwg.org/#request-class
* https://fetch.spec.whatwg.org/#response-class
* https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c
* https://googlechrome.github.io/samples/service-worker/
216
* https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/
217
* https://storage.spec.whatwg.org/
218 219 220

## Retex

Cerdic's avatar
Cerdic committed
221 222 223 224 225
* https://www.captechconsulting.com/blogs/my-experience-using-service-workers
* https://philna.sh/blog/2017/07/04/experimenting-with-the-background-fetch-api/
* https://www.twilio.com/blog/2017/02/send-messages-when-youre-back-online-with-service-workers-and-background-sync.html
* https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/
* https://www.construct.net/en/blogs/ashleys-blog-2/service-workers-are-a-pain-in-the-ass-934
226 227 228 229


## Libs/Frameworks

Cerdic's avatar
Cerdic committed
230 231 232 233
* https://developers.google.com/web/tools/workbox/
* http://daleharvey.github.io/Presentations/SOTR-Edinburgh-2014-06-06/#/60
* https://github.com/HubSpot/offline
* https://stackoverflow.com/questions/3181080/how-to-detect-online-offline-event-cross-browser
234 235 236

## Cours

Cerdic's avatar
Cerdic committed
237
* https://eu.udacity.com/course/offline-web-applications--ud899