Aller au contenu
HexaTransfer
Retour au blog
Approfondissements techniques

Service Worker : cache de fichiers pour transfert hors ligne

Utilisez les Service Workers pour activer le transfert de fichiers hors ligne : stratégies de cache, synchronisation en arrière-plan et PWA.

Un Service Worker maintient votre application de transfert opérationnelle même lorsque le réseau tombe : stockez le shell HTML et le JS avec l'API Cache, mettez en file d'attente les envois échoués avec Background Sync, conservez les fragments partiels dans IndexedDB et rejouez tout au retour de la connexion. Le worker tourne sur un thread séparé avec sa propre boucle d'événements, intercepte les événements fetch pour sa portée, et persiste même après la fermeture de l'onglet. Pour les outils d'envoi, la recette idéale combine une stratégie stale-while-revalidate pour le shell applicatif, une mise en file d'attente des fragments dans IndexedDB, et une inscription à Background Sync périodique qui retente les envois toutes les 15 minutes jusqu'au succès.

Ce que les Service Workers apportent aux applications de transfert

Le grand avantage est que navigator.serviceWorker survit aux rechargements d'onglets, aux coupures réseau, et même à la mise en veille du téléphone. Quand un utilisateur démarre l'envoi d'un fichier de 2 Go sur un Wi-Fi instable dans un train, vous voulez que les fragments déjà envoyés restent envoyés, que ceux en transit reprennent à la reconnexion, et que l'état complet soit récupérable si le navigateur tue l'onglet pour libérer de la mémoire. Un Service Worker — qui fonctionne indépendamment de tout onglet spécifique — est la pièce qui rend tout cela possible.

L'API vous donne trois composants fondamentaux : Cache pour stocker les réponses par URL, IndexedDB pour les données structurées (files de fragments, état de session), et SyncManager pour planifier des tentatives qui se déclenchent dès que l'appareil est en ligne.

Inscription et gestion des versions du worker

Inscrivez-vous une fois au chargement de l'application et gérez les mises à jour explicitement :

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js', { scope: '/' })
    .then((reg) => reg.addEventListener('updatefound', () => {
      const sw = reg.installing;
      sw.addEventListener('statechange', () => {
        if (sw.state === 'installed' && navigator.serviceWorker.controller) {
          // nouvelle version prête, inviter l'utilisateur à rafraîchir
        }
      });
    }));
}

Versionnez vos clés de cache (transfer-v7) pour qu'un nouveau déploiement invalide les anciens assets sans laisser de JavaScript périmé. Le bug classique : index.html mis en cache indéfiniment, les utilisateurs ne reçoivent jamais la mise à jour. Liez les caches aux hashes de build et nettoyez dans l'événement activate.

Stratégies de cache : shell applicatif contre données utilisateur

Chaque type de ressource mérite une stratégie différente :

  • Shell applicatif (HTML, CSS, JS, icônes) : cache-first avec fallback réseau. Chargements instantanés, fonctionne hors ligne.
  • Métadonnées API (/shares/:id) : network-first avec fallback cache, TTL de 60 secondes. Fraîches en ligne, utilisables hors ligne.
  • Octets de fichiers : ne jamais mettre en cache. Les fichiers font souvent plusieurs gigaoctets et l'API Cache a des quotas par origine (typiquement 60 % du disque libre).
  • Polices depuis des CDN : stale-while-revalidate. Rapides et auto-actualisées.

Dans le gestionnaire fetch :

self.addEventListener('fetch', (e) => {
  const url = new URL(e.request.url);
  if (url.pathname.startsWith('/assets/')) {
    e.respondWith(cacheFirst(e.request, 'shell-v7'));
  } else if (url.pathname.startsWith('/api/shares/')) {
    e.respondWith(networkFirst(e.request, 'api-v1', 60));
  }
});

Ne jamais intercepter les requêtes d'envoi de fichiers binaires — contournez-les en vérifiant e.request.method === 'PUT' et en sortant immédiatement. Proxifier des PUT de plusieurs gigaoctets via le worker est un désastre mémoire.

Mise en file d'attente des envois échoués avec Background Sync

SyncManager est la clé d'envois résilients. Quand un PUT de fragment échoue, stockez-le dans IndexedDB et inscrivez une synchronisation :

// dans le code de la page
const reg = await navigator.serviceWorker.ready;
await reg.sync.register('flush-uploads');
// dans sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'flush-uploads') {
    event.waitUntil(flushPendingUploads());
  }
});

Le navigateur déclenche l'événement sync dès que le réseau revient, avec un backoff exponentiel jusqu'à environ 24 heures. Chrome et Edge le supportent ; Safari a livré un sous-ensemble en version 17.5 sous le drapeau "Background Fetch" mais exige une autorisation utilisateur. Pour Safari, repliez-vous sur une nouvelle tentative à la prochaine ouverture de page visible via visibilitychange.

L'API Background Fetch est un outil distinct spécifiquement conçu pour les grandes opérations de fichiers — il affiche une notification persistante dans l'interface navigateur pour que les utilisateurs puissent suivre la progression même après avoir fermé l'onglet. Vaut la peine d'être utilisé pour les envois de plus de 500 Mo.

Stockage des envois partiels dans IndexedDB

IndexedDB est votre espace de travail durable. Ouvrez une petite base de données une fois, puis stockez les métadonnées de session et les décalages de fragments :

const db = await openDB('transfers', 1, {
  upgrade(db) {
    db.createObjectStore('sessions', { keyPath: 'id' });
    db.createObjectStore('chunks', { keyPath: ['sessionId', 'index'] });
  }
});
await db.put('sessions', {
  id, fileName, fileSize, fileFingerprint, createdAt: Date.now(),
  completedIndexes: [], partUrls
});

Ne stockez pas les octets bruts des fragments — ils proviennent du handle File, qu'IndexedDB peut conserver comme référence de clone structuré valide après rechargement. Stocker le handle évite de dupliquer 2 Go d'octets dans la base de données.

Le stockage d'origine a des limites : environ 60 % du disque libre sur Chrome desktop, 1 Go par origine sur iOS Safari avant que la pression d'éviction se déclenche. Demandez navigator.storage.persist() pour obtenir le bucket « persistant » que les navigateurs évitent d'évincer automatiquement.

Gestion des transitions hors ligne et en ligne

Écoutez les événements online et offline, à la fois dans la page et dans le service worker :

// page
window.addEventListener('online', () => {
  ui.showBanner('De retour en ligne — reprise des envois');
  navigator.serviceWorker.controller?.postMessage({ type: 'resume' });
});
window.addEventListener('offline', () => {
  ui.showBanner('Hors ligne — envois en pause');
});

navigator.onLine est notoirement peu fiable sur les portails captifs d'entreprise — il rapporte true quand l'appareil a une connexion réseau locale mais pas d'accès internet. Pour une détection fiable, faites un petit fetch('/ping', { cache: 'no-store' }) avec un timeout de 3 secondes.

Transformer l'application en PWA complète

Livrez un manifest.json avec display: standalone, un jeu d'icônes, et start_url: /. Ajoutez des liens apple-touch-icon pour iOS. Déclarez la gestion de fichiers pour que l'OS puisse associer votre application à des extensions spécifiques :

{
  "name": "HexaTransfer",
  "file_handlers": [{
    "action": "/share-target",
    "accept": { "application/*": [".pdf", ".zip", ".docx"] }
  }]
}

Combiné avec Web Share Target, cela permet aux utilisateurs de partager des fichiers depuis la feuille de partage de leur OS directement dans votre application. Sur Chrome Android et Chromium desktop, la PWA peut s'inscrire comme gestionnaire par défaut pour les types de fichiers déclarés — transformant une page navigateur en outil natif d'envoi.

Tester le comportement hors ligne

Trois scénarios à tester manuellement, car les tests automatisés hors ligne sont fragiles :

  1. Démarrez un envoi de 500 Mo sur Wi-Fi rapide, passez en mode avion à 30 %, attendez 30 secondes, réactivez le Wi-Fi. L'envoi doit reprendre là où il s'est arrêté sans action utilisateur.
  2. Démarrez un envoi, fermez l'onglet à 60 %, attendez 2 minutes, rouvrez. Proposez de reprendre la session.
  3. Démarrez un envoi sur mobile, verrouillez l'écran 5 minutes. Background Sync doit se déclencher au déverrouillage et terminer le transfert.

La case à cocher « Offline » des Chrome DevTools et Application > Service Workers > Update on reload sont indispensables. Les profils « Throttling » du panneau Network permettent de simuler Fast 3G et Slow 3G pour voir comment votre interface d'erreur se comporte.

HexaTransfer utilise un Service Worker pour le cache du shell applicatif et IndexedDB pour l'état de session en cours, afin que les rechargements et les brèves coupures réseau ne fassent pas perdre la progression des envois. Essayez-le sur https://hexatransfer.com — gratuit, sans compte, 10 Go maximum.

Pièges courants à connaître

Les Service Workers ont quelques subtilités qui piègent les développeurs novices : ils ne fonctionnent que sur HTTPS (sauf localhost), les quotas de cache varient considérablement selon les navigateurs, iOS Safari ne réveille pas fiablement les workers pour Background Sync, les DevTools peuvent mettre en cache des workers périmés de manière agressive (activez toujours « Bypass for network » en développement), et importScripts s'exécute de manière synchrone lors de l'installation — n'y récupérez jamais de scripts tiers lents. Écrivez un petit test d'intégration qui vérifie que le worker s'active, revendique des clients et sert la page hors ligne — ce seul test détecte 80 % des régressions que vous rencontrerez en production.

Envoyez vos fichiers volumineux en toute sécurité avec le chiffrement de bout en bout

Transférez des fichiers jusqu'à 10 Go gratuitement avec le chiffrement de bout en bout. Aucun compte requis. Vos fichiers sont chiffrés dans votre navigateur avant l'envoi — personne d'autre ne peut les lire.

Envoyer un fichier