Skip to content

PWA Yin Shi – Mise à jour fiabilisée & site de test DreamHost

🎯 Objectifs

  1. Fiabiliser la mise à jour de la structure de la PWA (shell Flutter web, JS/CSS/HTML), pour éviter les situations où certains utilisateurs restent sur une ancienne version.
  2. Mettre en place un site de test/staging à côté du site de production sur DreamHost, avec un flux de déploiement clair.

Ce document décrit une démarche progressive pour atteindre ces deux objectifs en s’appuyant sur l’existant :

  • Build Flutter web (flutter build web --release --base-href /web/).
  • Déploiement via rsync sur DreamHost (/home/farid/yinshi.app/web).
  • Fichier version.txt écrit à chaque déploiement (script local + GitHub Actions).
  • Service worker généré automatiquement par Flutter pour la PWA.

0. Statut (décembre 2025)

  • Étape 1 – Mise à jour PWA fiable (Web) est implémentée pour l’environnement de test :
  • serveur de test : https://yinshi-test.yinshi.app/ (déploiement via ./scripts/deploy_web.sh --env test),
  • version.txt contient désormais la version pubspec.yaml (ex. 1.0.24+40),
  • VersionService côté Flutter Web lit version.txt (avec un paramètre ?ts= pour éviter le cache) et compare la version distante avec la version locale (AppVersionInfo).
  • UX Web :
  • au démarrage (et lorsqu’un nouveau service worker est installé), un contrôle de version est effectué ;
  • si une version distante plus récente est détectée :
    • un dialog de mise à jour s’affiche (boutons Mettre à jour / Plus tard),
    • un bandeau discret en haut de l’écran rappelle la disponibilité de la mise à jour en cas de clic sur Plus tard.
  • Android / iOS :
  • le nouveau code reste isolé au Web (imports conditionnels / kIsWeb),
  • le comportement de mise à jour mobile existant n’est pas modifié pour cette étape.

Les sections suivantes décrivent le contexte général et les étapes prévues (y compris les évolutions futures qui ne sont pas encore toutes implémentées).


1. État actuel (rappel)

1.1 Build & déploiement

  • Script local scripts/deploy_web.sh :
  • Build : flutter build web --release --base-href /web/.
  • Déploiement : rsync --delete build/web/ farid@iad1-shared-b7-36.dreamhost.com:/home/farid/yinshi.app/web.
  • Écriture d’un version.txt distant via SSH avec git describe --tags --always (ou timestamp).

  • Workflow GitHub Actions .github/workflows/deploy-web.yml :

  • Utilise subosito/flutter-action@v2 (Flutter 3.35.5).
  • Build : flutter build web --release --base-href ${{ secrets.DEPLOY_BASE_HREF || '/' }}.
  • Déploiement : rsync --delete build/web/ $REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/.
  • Écrit également un version.txt avec la même logique.

➡️ Côté serveur : à chaque déploiement, l’arborescence build/web est proprement remplacée, et version.txt contient l’identifiant de version déployé.

1.2 Côté PWA / navigateur

  • web/index.html est minimal (base href, manifest, script flutter_bootstrap.js).
  • web/manifest.json décrit les icônes/couleurs mais ne porte pas de logique de version.
  • Le service worker Flutter par défaut est généré dans build/web/ (pas de SW custom dans le repo).

Comportement du service worker Flutter :

  • Cache index.html, main.dart.js, assets… en mode offline‑first.
  • Lors d’un nouveau déploiement :
  • Une nouvelle version du SW est téléchargée.
  • Elle passe en état waiting.
  • Elle n’est activée que lorsque tous les onglets utilisant l’ancienne version sont fermés, sauf si on force skipWaiting.

➡️ Effet pour les utilisateurs :

  • Certains voient immédiatement la nouvelle version (nouvel onglet, hard reload, etc.).
  • D’autres restent sur l’ancienne version tant qu’ils gardent l’onglet ouvert.
  • Aucune logique actuelle ne :
  • lit version.txt côté client,
  • surveille le service worker,
  • propose un dialog de mise à jour ou un reload automatique.

2. Objectif A – Fiabiliser la mise à jour de la PWA

Vue d’ensemble

On veut un comportement du type :

  1. L’utilisateur ouvre https://yinshi.app/web/.
  2. L’app démarre normalement (version N).
  3. En tâche de fond, l’app compare sa version locale avec la version déployée sur le serveur (version.txt).
  4. Si une nouvelle version (N+1) est détectée :
  5. soit l’app affiche un dialog "Nouvelle version disponible – Recharger ?";
  6. soit elle recharge automatiquement (mode testeurs).
  7. Le reload force alors la prise en compte du nouveau service worker et des nouveaux assets.

Pour y arriver, on s’appuie sur deux leviers :

  • version.txt comme source de vérité côté serveur.
  • API Service Worker du navigateur pour déclencher/accélérer la mise à jour.

2.1 Étape 1 – Formaliser la version locale côté Flutter

But : exposer dans le code Flutter une information de version locale, comparable à version.txt.

Pistes :

  • Lire la version depuis pubspec.yaml au build (par ex. via un script de build ou un fichier Dart généré) et exposer :
  • soit versionName type 1.2.3 ;
  • soit un identifiant plus technique (tag Git, hash court, etc.).
  • Option simple :
  • réutiliser le même identifiant que ce qui est écrit dans version.txt (git describe --tags --always).

Décision à prendre :

  • Forme de la version à afficher à l’utilisateur (ex : v1.2.3).
  • Forme de la version technique de comparaison (tag/hash exact de version.txt).

2.2 Étape 2 – Créer un VersionService dans Flutter

But : avoir un service unique qui sait :

  • charger la version locale (exposée via constante ou fichier généré),
  • faire un HTTP GET sur https://yinshi.app/web/version.txt,
  • normaliser/trim la valeur retournée,
  • comparer et exposer un résultat du type :
  • sameVersion,
  • newerRemoteVersion,
  • error (network, parse, etc.).

Comportement recommandé :

  • Appeler ce service :
  • au démarrage de l’app (après l’écran de splash ou sur le premier écran stable),
  • éventuellement à intervalle régulier pour les sessions longues (facultatif).
  • Gérer les erreurs réseau de manière silencieuse (pas de blocage de l’UI si version.txt inaccessible).

2.3 Étape 3 – UX de mise à jour (dialog/snackbar)

But : informer l’utilisateur de manière claire lorsqu’une nouvelle version est disponible.

Options :

  1. Snackbar discrète en bas :
  2. Texte : "Nouvelle version disponible".
  3. Bouton : Recharger.
  4. Dialog bloquant (recommandé pour des changements critiques) :
  5. Titre : "Nouvelle version de Yin Shi disponible".
  6. Corps : résumé court (ou lien vers les notes de version plus tard).
  7. Boutons :
    • Mettre à jour maintenant → déclenche un reload.
    • Optionnel : Plus tard → ferme le dialog (mais garde un indicateur discret).

Action technique lorsque l’utilisateur clique sur "Mettre à jour" :

  • Côté web (Flutter), déclencher un window.location.reload() pour recharger l’app et laisser le service worker prendre la nouvelle version.

2.4 Étape 4 – Intégrer l’API Service Worker

But : ne pas dépendre uniquement de la "magie" du cache, mais piloter le cycle de vie du service worker.

Actions possibles :

  1. Forcer une vérification de mise à jour :
  2. navigator.serviceWorker.getRegistration() puis registration.update().
  3. À appeler typiquement :

    • au démarrage de l’app,
    • juste avant de vérifier version.txt,
    • ou quand l’utilisateur demande explicitement une mise à jour.
  4. Détecter l’arrivée d’un nouveau service worker :

  5. écouter registration.onupdatefound + installing.onstatechange ;
  6. quand l’état devient installed alors qu’un contrôleur existe déjà, ça signifie qu’une nouvelle version de la PWA est prête.

  7. Coupler cela avec l’UX :

  8. lorsqu’un nouveau SW est installed, afficher le dialog "Nouvelle version disponible" (cf. 2.3) ;
  9. si l’utilisateur accepte → reload.

Cette logique peut être :

  • soit mise en place en Dart (via dart:html dans Flutter web),
  • soit ajoutée en JavaScript dans web/index.html (puis exposer un hook minimal côté Flutter si nécessaire).

2.5 Étape 5 – Auto‑reload sur controllerchange (optionnel mais puissant)

But : éviter toute incohérence lorsque le service worker actif change.

Dans web/index.html, on peut ajouter un petit script JS générique qui :

  • écoute navigator.serviceWorker.addEventListener('controllerchange', ...),
  • appelle window.location.reload() lorsqu’un nouveau contrôleur SW prend la main.

Effet :

  • Dès qu’un nouveau service worker devient actif, la page se recharge automatiquement.
  • Couplé au système de détection de version, ça donne un comportement très réactif.

Recommandation :

  • Activer cette logique d’abord en environnement de test/staging.
  • Si l’expérience est bonne, la déployer en production.

2.6 Étape 6 – Mode testeurs sans service worker

Pour des phases où l’on veut absolument que la dernière build soit servie à chaque chargement (ex. validation Google, tests intensifs) :

  • Builder la web app avec une stratégie PWA désactivée (ex. --pwa-strategy=none si supportée par la version de Flutter utilisée) pour un environnement de test.
  • Garder le service worker par défaut en production pour profiter du offline.

Des variantes possibles :

  • Utiliser un environnement de test (voir partie B) avec une build sans SW.
  • Garder la build PWA complète sur /web/ pour la production.

3. Objectif B – Site de test/staging sur DreamHost

On souhaite avoir deux environnements web distincts :

  1. Production : https://yinshi.app/web/
  2. Test/Staging :
  3. par exemple https://yinshi.app/web-test/,
  4. ou un sous‑domaine dédié (ex. https://test.yinshi.app/).

L’objectif est de :

  • pouvoir déployer rapidement sur l’environnement de test,
  • valider les nouvelles versions (PWA, mise à jour, UX),
  • puis déployer sereinement en production.

3.1 Étape 1 – Choisir l’URL du site de test

Deux options principales :

  1. Sous‑chemin :
  2. https://yinshi.app/web-test/
  3. Dossier distant : /home/farid/yinshi.app/web-test.
  4. --base-href /web-test/ côté Flutter.

  5. Sous‑domaine :

  6. https://test.yinshi.app/
  7. Dossier distant dédié (selon config DreamHost).
  8. --base-href / ou un chemin adapté.

Recommandation initiale (simple) :

  • Commencer par un sous‑chemin /web-test/, car :
  • configuration DreamHost plus simple (un nouveau dossier sous le même domaine),
  • réutilisation directe de la logique actuelle (rsync, SSH, etc.).

3.2 Étape 2 – Préparer la structure sur DreamHost

Pour un sous‑chemin /web-test/ :

  1. Créer un dossier sur le serveur :
  2. /home/farid/yinshi.app/web-test.
  3. Vérifier que la config Apache/Nginx (gérée par DreamHost) sert bien ce dossier via l’URL https://yinshi.app/web-test/.
  4. Éventuellement adapter .htaccess si nécessaire pour gérer les routes Flutter web (spa) dans ce sous‑chemin.

3.3 Étape 3 – Adapter le script de déploiement local

Objectif : permettre un déploiement vers prod ou test avec un seul script.

Approche suggérée :

  • Modifier scripts/deploy_web.sh pour accepter un paramètre d’environnement, par exemple :
  • --env prod (par défaut),
  • --env test.

Pour chaque environnement :

  • prod :
  • REMOTE_PATH="/home/farid/yinshi.app/web"
  • FLUTTER_OPTS="--release --base-href /web/"
  • URL : https://yinshi.app/web/
  • version.txt écrit dans /web/version.txt.

  • test :

  • REMOTE_PATH="/home/farid/yinshi.app/web-test"
  • FLUTTER_OPTS="--release --base-href /web-test/"
  • URL : https://yinshi.app/web-test/
  • version.txt écrit dans /web-test/version.txt.

Checklist :

  • S’assurer que le script :
  • construit avec les bonnes options FLUTTER_OPTS selon --env,
  • synchronise sur le bon dossier avec rsync,
  • écrit version.txt dans le bon chemin distant.

3.4 Étape 4 – Adapter le workflow GitHub Actions

Objectif : avoir au moins deux workflows ou un workflow paramétrable :

  1. Déploiement test/staging (ex. déclenché sur push sur une branche develop ou feature/* + workflow_dispatch).
  2. Déploiement production (ex. déclenché manuellement ou sur push avec tag/release).

Éléments à prévoir :

  • Secrets séparés ou au minimum :
  • DEPLOY_REMOTE_PATH_TEST pour /home/farid/yinshi.app/web-test.
  • DEPLOY_REMOTE_PATH pour la prod (/home/farid/yinshi.app/web).
  • DEPLOY_BASE_HREF_TEST = /web-test/.
  • DEPLOY_BASE_HREF = /web/.
  • Job qui :
  • installe Flutter,
  • exécute flutter pub get + flutter test,
  • build web avec le bon --base-href,
  • déploie avec rsync sur le bon chemin,
  • met à jour version.txt sur l’environnement visé.

Recommandation :

  • Commencer par un workflow distinct pour le test (plus simple à raisonner),
  • Puis éventuellement fusionner dans un workflow paramétrable par input (env: test|prod).

3.5 Étape 5 – Gestion des URLs côté Flutter

Certaines URLs sont actuellement en dur dans le code, par exemple dans lib/services/payment_plan_service.dart :

  • https://yinshi.app/web
  • https://yinshi.app/web/#/premium-plans

Avec deux environnements web, il faudra décider :

  • soit d’utiliser les mêmes URLs backend (prod) mais des URLs de front différentes selon env,
  • soit d’introduire une configuration par environnement (prod vs test) pour ces URLs (utile notamment pour Stripe ou d’autres intégrations).

Piste :

  • Utiliser un fichier de config ou des constantes séparées par environnement (ex : EnvironmentConfig) et un flag de build (--dart-define) pour choisir entre prod et test.

4. Ordre recommandé des travaux

Phase 1 – Mise à jour fiable en production (sans site de test)

  1. Formaliser la version locale exposée dans Flutter.
  2. Créer le VersionService qui lit version.txt et compare.
  3. Ajouter l’UX de mise à jour (snackbar/dialog) + action Recharger.
  4. Intégrer une première interaction avec le service worker :
  5. au moins registration.update() au démarrage,
  6. éventuellement détection d’un SW installed.

Objectif :

  • Même sur l’URL actuelle https://yinshi.app/web/, la mise à jour devient prévisible (l’app sait lorsqu’une nouvelle version est déployée et propose un reload).

Phase 2 – Site de test/staging

  1. Choisir l’URL de test (ex. https://yinshi.app/web-test/).
  2. Créer le dossier distant /home/farid/yinshi.app/web-test sur DreamHost.
  3. Adapter le script deploy_web.sh pour gérer --env test.
  4. Créer un workflow GitHub Actions staging qui déploie sur /web-test/.
  5. Tester la PWA de staging :
  6. vérifier version.txt sur /web-test/version.txt,
  7. vérifier les routes Flutter,
  8. expérimenter les changements de service worker (auto‑reload, etc.).

Phase 3 – Ajustements avancés PWA

  1. Ajouter le listener controllerchange dans index.html si l’expérience est satisfaisante.
  2. Éventuellement introduire un mode sans service worker côté test pour certaines campagnes de validation.
  3. Mettre en place une gestion de config d’URLs par environnement (Stripe, redirections, etc.).

5. Checklist rapide

Pour la mise à jour fiable de la PWA

  • [ ] Exposer une version locale côté Flutter (alignée sur version.txt).
  • [ ] Implémenter un VersionService qui lit version.txt depuis le serveur.
  • [ ] Comparer local vs distant au démarrage et au besoin.
  • [ ] Afficher une snackbar ou un dialog "Nouvelle version disponible".
  • [ ] Sur action utilisateur, recharger la page (window.location.reload()).
  • [ ] Intégrer au minimum serviceWorker.update() au démarrage.
  • [ ] (Optionnel) Ajouter un listener controllerchange dans index.html pour auto‑reload.

Pour le site de test sur DreamHost

  • [ ] Choisir l’URL de test (sous‑chemin ou sous‑domaine).
  • [ ] Créer le dossier distant (ex. /home/farid/yinshi.app/web-test).
  • [ ] Adapter deploy_web.sh pour gérer --env test (REMOTE_PATH + base href).
  • [ ] Créer/adapter le workflow GitHub Actions pour staging.
  • [ ] Vérifier que https://yinshi.app/web-test/ sert bien la PWA de test.
  • [ ] Vérifier que /web-test/version.txt reflète correctement les déploiements de test.

Ce document sert de plan directeur. Chaque étape peut être implémentée progressivement.

Pour la suite, il est recommandé de :

  1. Commencer par la Phase 1 (détection de version + dialog de mise à jour) directement sur l’environnement actuel.
  2. Ajouter ensuite le site de test pour pouvoir expérimenter des comportements PWA plus agressifs (auto‑reload, désactivation du SW, etc.) sans impacter les utilisateurs finaux.