[spip-dev] [conception] Un plugin Progressive Web App commun en coquille vide ? (1 SW 2 rule dem all)

Après une semaine de diverses recherches, voici où j'en suis. Celleux qui s'intéressent à la question (notamment Cédric car Offline), merci de me dire ce que vous en pensez. :slight_smile:

Rappel : une PWA, ce n'est pas une fonctionnalité précise, mais un ensemble de fonctionnalités JS qui augmentent les capacités d'un site web pour lui permettre des choses que seules les applis natives pouvaient faire avant. Et comme son nom l'indique : ça peut être progressif, on peut ajouter petit à petit des choses. Ça peut être :
- garder en mémoire des pages pour lire même déconnecté
- synchroniser des données régulièrement
- s'inscrire à des notifications push (qui arrivent donc même quand on n'a pas ouvert le site)

Point important 1 : on peut vouloir *une seule ou plusieurs* de ces fonctionnalités. C'est forcément modulaire, ya aucune obligation d'avoir tout.

Cependant elles utilisent un truc commun : un service worker, un programme JS qui tourne chez la personne en parallèle du site.

Point important 2 : il ne peut y avoir QU'UN service worker pour une même "branche" de l'arbo des URL. Et par défaut la portée est celle du dossier/URL où se trouve le fichier JS. Par exemple s'il est dans exemple.com/javascript/sw.js alors par défaut il ne pourrait servir que pour les requêtes sous exemple.com/javascript/… Si le fichier est à la racine du site, alors la portée par défaut est "tout le site".

Le plugin Offline de Nursit, fait pour le Diplo et utilisé en test grandeur nature sur Contrib, ajoute la fonctionnalité "lecture hors ligne" :

(au passage maintenant qu'on a git, est-ce qu'il pourrait être dans la communauté ?)

Ce plugin ajoute donc UN service worker, pour TOUT le site, mais seulement pour la fonctionnalité "hors ligne". Du coup quand on veut ajouter une autre fonctionnalité, les pushs par ex, il est impossible d'avoir un autre SW pour la même portée. J'ai bien cherché s'il était possible de coder les pushs dans un SW avec une portée bidon car ya pas à capter les requêtes (événement "fetch") on s'en fout. Mais impossible d'avoir une réponse exacte à ça et la doc dit plutôt le contraire :

Cet événement est envoyé au scope global d'un ServiceWorker

Je propose donc que SPIP ait un plugin "pwa" commun, qui va mutualiser :
- la génération d'un fichier JS de service worker ayant une portée globale
- mais ce fichier serait VIDE par défaut, et ce sont à des sous-plugins comme Offline ou Webpush d'ajouter du JS pour capter des événements dedans (qui "fetch", qui "push", etc)
- l'inscription de ce SW dans le site
- la désinstallation
- la gestion de la version, comme le fait déjà Offline et donc la mise à jour chez les gens : cela se ferait si on ajoute ou retire un sous-plugin qui s'insère dans son JS + quand des sous-plugins le demandent en plus (comme Offline quand on a une version de site qui change)
- comme ce serait LE plugin commun, c'est aussi là qu'on déclarerait le "manifest" permettant d'installer le site-appli sur l'accueil des mobiles (comme tout autre appli)

Il faudra donc modifier Offline pour s'insérer dedans, et non plus fournir son propre service worker.

Hello,

pour répondre "oui et non" parce que la vie c’est pas simple :slight_smile:

Pour le Oui :

1/ il y a dans offline déjà tout le mécanisme dont tu parle dans https://git.nursit.net/open/offline/-/blob/master/action/api_offline.php :

• l’api avec routeur vers des methodes extensibles
• le builder de l’installeur, du service worker
• la gestion de l’installation, la désinstallation via https://git.nursit.net/open/offline/-/blob/master/offline_pipelines.php

2/ il suffirait donc de l’extraire en le prefixant de manière plus générique en offrant des pipelines pour construire la liste des JS a inclure dans le build (mais il faut construire une liste de JS ET une liste d’arguments à passer, qui seront ajoutés sous forme de tableau de config js)

3/ il manque un mécanisme d’auto-destruction du service worker
C’est fil qui me l’avait suggéré, et au retour d’expérience ça semble totalement indispensable : quand on envoie un service worker chez le navigateur client, il s’installe et a sa propre vie, autonome. Si on envoie un bug, notamment autour de la mise à jour, c’est mort : le service worker ne se mettra jamais à jour, le client est planté à jamais, sauf à faire un shift+reload, ce qu’un•e utilisateur•ice normal•e ne fera jamais.

La bonne parade c’est donc que le service worker soit toujours envoyé avec une date de péremption, éventuellement configurable. Cela l’oblige à se mettre à jour tous les X jours, mais cela garanti aussi qu’en cas de gros bug les effets indésirables ne dureront pas plus longtemps

Pour le Non :

1/ de ce que j’ai vu dans l’utilisation des services workers c’est que c’est vraiment pas simple à faire marcher de façon fiable, super compliqué à débuguer vu que tout se passe dans les navigateurs clients, avec toute leur diversité, et qu’on a aucune info en feedback (à part quelqu’un•e qui va dire « ça marche pas » ou « je tombe tout le temps sur la page 404 », sans moyen de savoir ce qu’il y a de stocké dans son navigateur)

2/ de là je suis très frileux à voir plusieurs plugins ajouter leur JS et vogue la galère en espérant que tout marche bien à l’aveugle alors que déjà tu as du mal à faire marcher le truc correctement en maitrisant tout le code (autant croire au miracle)

3/ je trouve que ces plugins qui reposent sur un plugin qui doivent installer un plugin… etc pour s’installer c’est bien lourd. J’ai encore un peu en travers de la gorge le mini-calendrier extrait du plugin agenda « parce que bon c’est générique et d’autres en auront besoin » qui pourrit la vie des utilisateur•ices du plugin agenda depuis à peu près 14 ans sans que jamais aucun autre plugin n’ait utilisé ce fameux mini-calendrier

4/ je sais pas trop comment proposer une solution propre de partage

En conclusion j’ai envie de dire : avant de faire un plan à 20 ans pour que tout puisse s’assembler dans un monde idéal, et alors qu’on a pas de retour d’expérience sur le sujet, avançons donc sur les différents sujets en parallèle, et quand on comprendra tout comment ça marche bien, on aura sans doute une idée plus claire de ce qu’il faut mutualiser, ce qu’il faut séparer etc...

(et peut-être que d’ici là on aura avancé sur l’utilisation de composer ?)

Merci pour ces réflexions !

Alors je pourrais être d'accord (pas faire de la sur-qualité) si… c'était possible. :slight_smile:

Car comme je le dis au début, la volonté de concevoir un truc modulaire concerne plusieurs cas :
- on peut ne vouloir qu'une seule des fonctionnalités (que l'offline, que les pushs, que de la synchro de données, etc)
- on peut vouloir plusieurs de ces fonctionnalités dans le même site

Présentement, c'est le deuxième pour moi : j'ai absolument besoin que les gens puissent lire hors-ligne *et* pouvoir s'abonner à des "flux" de notifs en push.

Alors comment "travailler en parallèle" si de base le fait d'avoir Offline empêche un autre plugin d'avoir aussi un service worker avec une portée "root" (tout le site) ?

Est-ce que l'idée ça serait que le plugin de Push soit pour l'instant forcément dépendant de Offline (donc sans plugin central mutualisateur) et que donc il doive surcharger "en dur" (pas par pipelines je veux dire) certaines fonctions de Offline ?
Un truc du genre : surcharger "action_api_offline_sw_js_dist()" pour ajouter un fichier JS à la liste, qui contiendrait "self.addEventListener('push'" + "self.addEventListener('notificationclick'" etc

Restera aussi la question de où mettre et rendre configurable (couleur, icône) le Manifest qui permet d'installer vraiment.

(répondre sur la liste :p)

Ah ben si c’est ça ton soucis et ton use case de maintenant, on peut ajouter facilement une extensibilité de offline.

Bah disons que c'est *un* des soucis (pour avant-hier :p)

J'ai bien vu déjà tout le bazar de gestion complète (installation, mise à jour, désinstallation) du service worker dans Offline, qui est déjà pas mal complet et ça c'est super !

Au delà de l'aspect choix des utilisateurs (proprio qui veut une seule ou plusieurs fonctionnalités nécessitant un SW) qui implique une conception modulaire, même juste de notre point de vue dév maintenance du code, vu que d'autres événements (push ou autres) en ont obligatoirement besoin aussi, je m'étais dit que ça serait justement peut-être plus clair, plus facile à maintenir si c'est découpé :
- tout le code qui est propre à la gestion, quelque soit le contenu
- VS le code propre à chaque besoin, Offline qui gère le fetch etc
(par ex il pourrait y avoir des logs différents pour chaque morceau et donc suivant ce qu'on voit, on saurait si ça vient du plugin central ou de Offline ou de Push).

Mais pour l'instant de ton côté tu as l'air de penser que ça serait plus compliqué, ce que je comprends. :slight_smile:

(Et pour revenir sur le point Non-2, je ne crois pas qu'il y ait "plusieurs plugins" au sens "plein" qui vont s'insérer dedans et qu'on contrôle rien : ce sont forcément quelques rares plugins qui "se connaissent" et qui vont faire en sorte de travailler ensemble, genre 2 ou 3 max. Le découpage proposé est là pour permettre d'avoir seulement ce qu'on veut, car mise à part le SW au cœur, chacun n'a aucun rapport, c'est des fonctionnalités totalement différentes, possiblement avec des tables SPIP, etc.)

À court terme, si déjà un plugin Push peut profiter immédiatement de ce qui existe et gérer ses événements à lui dans le SW, moi ça me va hein. On pourra découper mieux plus tard…

Mais comme je le disais, offline n’est pas encore super fini, il reste des soucis, il y a besoin de debug, ce qui prends du temps, et je suis moyennement confiant sur le fait qu’ajouter des js en plus aide...

Bé pour un site j'ai vraiment besoin de Offline *et* de Push, donc je vois pas comment on… enfin au moins moi, je pourrais faire l'économie d'avoir plus de JS… :frowning:

Pour Offline en particulier ça tombe bien, on peut aider à faire des retours, puisque besoin sur un site avec pas mal d'articles et beaucoup de visites.

Une des grosses différences que je crois voir, c'est que pour le Diplo c'est un mensuel, et donc peu de changement éditoriaux (on peut se permettre de changer manuellement la "version éditoriale" quand un nouveau numéro sort), alors que nous ya des nouveaux articles tous les jours, possiblement plusieurs fois par jour (l'accueil change tout le temps quoi). Et ya évidemment besoin que les gens puissent garder en mémoire au moins l'accueil + les articles liés dans l'accueil, quand ils ont une connexion, pas juste une fois par mois.

Mais bon on verra plus tard pour Offline, la priorité c'était surtout la structure générale, et le fait de pouvoir avoir *en même temps* qu'Offline un autre plugin qui permet de s'abonner à des pushs. :slight_smile:

@rasta a estimé :

le Diplo c’est un mensuel, et donc peu de changement éditoriaux (on peut se permettre de changer manuellement la « version éditoriale » quand un nouveau numéro sort).

Non mais enfin… mais si, la une change tous les jours, même plusieurs fois. Et c’est d’ailleurs un des soucis avec le plugin offline, la mise à jour de la version éditoriale est complexe (ligne de commandes plus vider le cache plus…, j’ai fini par me décourager en testant, il faudrait que je m’y remette).

A part ça, pourquoi ne pas ajouter les push à Offline ?

BoOz

Bé je l'ai dit plusieurs fois non ? :slight_smile:
Parce qu'à part le service worker en centre, ça n'a strictement aucun rapport, qu'on peut parfaitement vouloir des pushs sans avoir besoin d'offline, comme l'inverse, ou autre fonctionnalité de PWA (la synchro de données en arrière plan… tout ce qui a besoin d'ajouter un event dans le SW). Chacune des ces fonctionnalités peut avoir pleiiin de code, peut avoir besoin de tables dans SPIP, etc. Donc tout mélanger dans le même plugin, vla le bazar tout-en-un à maintenir…

Ce point m'interroge : je croyais en lisant la doc que justement il y avait *dans l'interface* de la config, un "numéro éditorial" changeable manuellement (donc pas un admin sys), et que changer ce champ permettait de forcer la mise à jour du contenu chez les gens.

Et pas juste la doc, c'est l'explication collée au champ même : "Version éditoriale. Le changement de numéro de version force la mise à jour de toutes les pages en cache chez tous les visiteurs."

Du coup ce que tu dis pour le Diplo parait bizarre, ça ne marche pas ?

Faut lire TOUTE la doc, et notamment la partie https://git.nursit.net/open/offline#build

Le numero de version editoriale, c’est un flag qu’on modifie et ça permet de forcer une mise à jour chez les internautes.
Mais potentiellement il faut remettre à jour la listes des urls à télécharger, en particulier si tu as des ressources générées automatiquements (css, js..) dont les URLs peuvent changer (si tu as fais des modifs de squelette), ou si tu veux que les visiteurs téléchargent par défaut les derniers articles ou…

bref ça dépend aussi comment tu veux utiliser le plugin

Ok alors pour cette liste de démarrage : y a-t-il un squelette surchargeable permettant de personnaliser la liste des pages (justement pour "les derniers articles" ou plus précisément "la même liste que ceux qu'on voit sur l'accueil") ?

Car la doc parle de comment générer des boutons pour des objets entiers avec offline/urls-{objet}.html, ça ok. Mais pour la liste automatiquement chargée à l'installation ?
J'ai vu que dans la config ya un champ libre pour en personnaliser en plus à la main, mais ça c'est du ponctuel manuel. Pour faire comme les boutons pour objets, des automatismes (donc avec boucles) pour lister dans l'installation les mêmes que sur l'accueil, ya un squelette ou un pipeline PHP possible ?

Pour le build d'installation avec spip offline:build:services (ou le rebuild total avec les objets aussi), ya deux possibilités, pour que ce soit moins complexe pour Booz, et qu'il n'y ait que à changer le numéro dans l'admin :
- sur le serveur on peut programmer un vrai cron pour l'appeler une fois par nuit, et donc le lendemain ça enverra les nouveaux contenus aux gens, si ça suffit comme période
- si on veut encore plus de réactivité, on pourrait imaginer une option "--if-version-changed". Dans ce cas on pourrait programmer un vrai cron *toutes les minutes*, mais la commande sortirait immédiatement si ça détecte que la version éditoriale dans la config n'a pas changé depuis le dernier build. Évidemment pour pouvoir faire ça, il faut que lors d'un build, ça enregistre dans un fichier ou en base, le numéro de la version éditoriale utilisée à ce moment, pour pouvoir comparer ensuite.

yakafokon (tm)

On peut faire plein de choses pour simplifier, industrialiser, automatiser, mais bon, le premier point c’est de valider que tout marche : ie si je fais une nouvelle version avec des choses qui changent est-ce que la mise à jour se fait bien, est-ce que j’evite les liens brisés, est-ce il y a des utilisateurs qui restent scotchés avec du vieux contenus a vie, est-ce que..

Bref, avant de sortir la sulfateuse qui t’envoie une nouvelle version par minute, faudrait déjà faire en sorte que « envoyer une nouvelle version » ça marche correctement et bien, et c’est pas acquis.
Bref on en est là, de ce que j’ai vu sur contrib y a des bugs, et il faut passer du temps dessus pour les corriger.
Mais c’est pas un problème simple, et pour exemple je me suis payé pendant des mois des « pages introuvables » sur le site de francetvinfo à cause de leur serviceworker (ou d’une version bugguée que j’ai eu un moment dans mon navigateur) et il suffisait que je fasse f5 pour recharger et voir la page.

Le problème étant que tu as zéro information pour savoir ce qui se passe, que tu peux rien loger pour debug vu que tout se passe dans le navigateur du visiteur, et donc c’est un debug de longue haleine.
Quand ça sera totalement fiable on pourra voir la suite oui