12k
All articles

Uppy 6.0 et l'upload de fichiers avec reprise

Configurez des téléversements reprenables avec Uppy et Tus, découvrez les changements d’Uppy 6.0 pour S3 et les paquets core, et suivez les étapes de migration depuis la version 5.

OpenReplay Team
OpenReplay Team
Uppy 6.0 et l'upload de fichiers avec reprise

Uppy est un outil d’upload de fichiers JavaScript open source. Associé à son plugin @uppy/tus, il reprend les uploads volumineux après une coupure de connexion ou la fermeture d’un onglet : l’utilisateur n’a donc pas à renvoyer le fichier depuis le début.

La plupart des équipes se tournent vers une bibliothèque après le même rapport de bug : un utilisateur connecté au Wi-Fi d’un hôtel avait atteint 90 % de l’envoi d’une vidéo de 2 Go, la connexion a été coupée et l’upload est reparti de zéro. Si vous avez développé votre propre outil d’upload à partir de nos guides sur la création d’une zone de dépôt de fichiers ou l’upload de fichiers personnalisé dans React, c’est précisément à ce stade que cette approche montre ses limites.

Cet article explique l’intérêt des uploads avec reprise, présente une configuration minimale d’Uppy, détaille les nouveautés d’Uppy 6.0 et évalue l’effort nécessaire pour migrer depuis la version 5.x.

Points clés

  • Tus est un protocole ouvert, basé sur HTTP, qui permet de reprendre les uploads. Une coupure de connexion ou la fermeture d’un onglet ne fait pas perdre la progression : l’upload reprend à partir du dernier octet reçu par le serveur.
  • Uppy 6.0 réécrit @uppy/aws-s3 autour de trois modes de signature (getCredentials, signRequest et companionEndpoint), et chaque configuration en utilise exactement un.
  • Dans Uppy 6.0, @uppy/utils, @uppy/store-default, @uppy/companion-client et @uppy/provider-views sont désormais intégrés à @uppy/core sous forme de subpath exports.
  • Les projets qui utilisent le méta-package uppy ou le bundle CDN ne nécessitent que peu, voire aucune, modification de code pour passer à la version 6.0.
  • Une instance Companion auto-hébergée doit être mise à niveau vers Companion 7 avant que le client ne passe à @uppy/core 6, car les jetons OAuth transitent désormais par le WebSocket de Companion. Companion 7 s’appuie par ailleurs sur Express 5.

Pourquoi utiliser Uppy plutôt qu’un outil d’upload fait maison ?

Un outil d’upload maison reposant sur une simple requête POST via fetch ou XMLHttpRequest ne peut pas reprendre là où il s’est arrêté. En cas de coupure de connexion, tous les octets déjà envoyés sont perdus. Tus résout ce problème au niveau du protocole : le client demande au serveur combien d’octets il a déjà reçus, puis n’envoie que le reste. Le session replay permet de mesurer facilement le coût de l’absence de ce mécanisme : après un échec d’upload, on voit l’utilisateur revenir au sélecteur de fichiers et choisir à nouveau le même fichier volumineux.

Les uploads avec reprise via Tus nécessitent toutefois un composant côté serveur. La documentation de @uppy/tus recommande tusd, et les serveurs Tus peuvent stocker les fichiers en local ou sur un stockage externe comme S3.

Configuration minimale d’Uppy avec Dashboard et Tus

Trois packages suffisent pour obtenir un outil d’upload complet avec reprise : @uppy/core gère l’état, @uppy/dashboard fournit l’interface utilisateur et @uppy/tus se charge du transfert.

import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Tus from '@uppy/tus';
import '@uppy/core/css/style.min.css';
import '@uppy/dashboard/css/style.min.css';

new Uppy()
	.use(Dashboard, { inline: true, target: 'body' })
	// Point this at your own tus server, e.g. a tusd instance
	.use(Tus, { endpoint: 'https://your-tus-server/files/' });

inline: true affiche le Dashboard directement dans l’élément cible au lieu de l’ouvrir dans une fenêtre modale. Si vous préférez construire votre propre interface de progression, notre article sur la création d’une barre de progression d’upload présente les événements sous-jacents.

Les sources distantes comme Google Drive passent par Companion, le composant serveur d’Uppy. Companion récupère le fichier côté serveur, de sorte que les octets ne transitent jamais par le navigateur de l’utilisateur. Un détail a son importance : le paramètre chunkSize de @uppy/tus ne s’applique qu’aux fichiers envoyés depuis l’appareil de l’utilisateur. Les uploads qui passent par Companion suivent le paramètre chunkSize propre à Companion : définissez donc les deux si vous avez besoin de limiter la taille de chaque requête.

Par défaut, @uppy/tus relance l’envoi d’un chunk en échec après une série de délais et limite le nombre d’uploads simultanés. Lorsque le serveur renvoie une erreur HTTP 429, la logique onShouldRetry par défaut met en pause toutes les requêtes en file d’attente et patiente avant de réessayer, en parcourant les délais définis dans retryDelays.

Les nouveautés d’Uppy 6.0

Uppy 6.0 apporte trois changements majeurs : @uppy/aws-s3 est entièrement réécrit, quatre packages de support sont fusionnés dans @uppy/core et @uppy/instagram est supprimé. Selon l’article d’annonce d’Uppy 6.0, six packages ont fait l’objet d’une montée de version majeure : @uppy/core, @uppy/companion, @uppy/aws-s3, @uppy/tus, @uppy/components et uppy.

Dans Uppy 5, l’ancien plugin @uppy/aws-s3 comptait jusqu’à huit callbacks, et ceux dont vous aviez besoin variaient selon votre configuration : upload multipart ou simple, avec ou sans Companion, signature dans le navigateur ou sur un serveur. La réécriture remplace tout cela par trois modes de signature, parmi lesquels vous en choisissez exactement un :

ModeEmplacement de la signatureCompanion requis
getCredentialsDans le navigateur (SigV4), à l’aide d’identifiants temporaires fournis par votre endpointNon
signRequestVotre propre service de signatureNon
companionEndpointCompanionOui
import Uppy from '@uppy/core';
import AwsS3 from '@uppy/aws-s3';

new Uppy().use(AwsS3, {
	// Swap this for an R2, MinIO or Spaces endpoint
	s3Endpoint: 'https://my-bucket.s3.eu-west-1.amazonaws.com',
	region: 'eu-west-1',
	getCredentials: async () => fetch('/s3-credentials').then((r) => r.json()),
});

Companion n’est plus nécessaire pour uploader vers S3. Avec getCredentials, le navigateur signe lui-même chaque requête, et le plugin fonctionne avec n’importe quel service compatible S3, notamment Cloudflare R2, MinIO et DigitalOcean Spaces. La référence de @uppy/aws-s3 liste les champs que getCredentials doit renvoyer : dans l’exemple ci-dessus, votre endpoint /s3-credentials doit donc renvoyer exactement cette structure. L’article d’annonce précise également que la réécriture a corrigé des bugs de longue date concernant le backoff des nouvelles tentatives, l’expiration des identifiants, la détection du mode hors ligne ainsi que la mise en pause et la reprise des uploads multipart. Les tests CI du plugin s’exécutent désormais sur un véritable serveur MinIO plutôt que sur des mocks.

Comment migrer d’Uppy 5 vers Uppy 6 ?

Si vous utilisez le méta-package uppy ou le bundle CDN, la migration vers la version 6.0 ne nécessite que peu, voire aucune, modification de code. Dans les autres cas, suivez la liste ci-dessous ; le guide de migration officiel fournit tous les détails :

  • Packages individuels : remplacez les imports depuis @uppy/utils, @uppy/store-default, @uppy/companion-client et @uppy/provider-views par leurs subpaths dans @uppy/core. Les anciens packages sont marqués comme obsolètes sur npm.
  • Companion auto-hébergé : passez d’abord à Companion 7, puis migrez le client vers @uppy/core 6. Companion 7 reste compatible avec les anciens clients Uppy, mais un client Uppy 6 ne fonctionnera pas avec Companion 6 ou une version antérieure. Companion 7 repose sur Express 5 et ne fonctionnera pas comme middleware dans une application Express 4 : migrez donc votre application vers Express 5 avant de basculer. companion.socket() exige désormais companionOptions comme second argument. Les utilisateurs de Companion hébergé n’ont rien à faire.
  • Gestionnaires d’erreurs Tus : revérifiez votre code upload-error, car response est désormais renseigné.
- import { fetcher } from '@uppy/utils'
+ import { fetcher } from '@uppy/core/utils'

- import { RequestClient } from '@uppy/companion-client'
+ import { RequestClient } from '@uppy/core/companion-client'

CompanionClientProvider et CompanionClientSearchProvider ont également disparu. Importez Provider depuis @uppy/core/companion-client à leur place. Cette fusion vise à éviter un problème bien précis : un lockfile contenant une ancienne copie de @uppy/utils à côté d’un @uppy/core plus récent, ce qui aboutissait à deux copies du même package dans un même arbre de dépendances.

Depuis Uppy 6.0, @uppy/tus transmet le statut et le corps de la réponse du serveur à l’événement upload-error ainsi qu’à file.response. Les versions précédentes émettaient l’erreur sans la réponse du serveur.

uppy.on('upload-error', (file, error, response) => {
	if (error.isNetworkError) return showOfflineNotice(); // placeholder helper
	console.log(response?.status);
	console.log(response?.body.xhr.responseText);
});

response vaut toujours undefined dans deux cas : lorsque le serveur n’a jamais répondu (par exemple s’il est injoignable) ou lorsqu’il s’agit d’un fichier distant uploadé via Companion.

Utiliser Uppy avec React et d’autres frameworks

Les wrappers d’Uppy pour les différents frameworks permettent aux composants de s’abonner à l’état d’Uppy sans avoir à câbler manuellement les événements. Dans React, le hook useUppyState de @uppy/react prend en paramètres une instance Uppy et un sélecteur, et déclenche un nouveau rendu du composant lorsque la portion d’état sélectionnée change. La documentation de l’intégration React décrit l’API complète.

import { useState } from 'react';
import Uppy from '@uppy/core';
import Tus from '@uppy/tus';
import { useUppyState } from '@uppy/react';

function UploadStatus() {
	const [uppy] = useState(() =>
		new Uppy().use(Tus, { endpoint: 'https://your-tus-server/files/' }),
	);
	const files = useUppyState(uppy, (state) => state.files);
	const progress = useUppyState(uppy, (state) => state.totalProgress);
	return <p>{Object.keys(files).length} files, {progress}% uploaded</p>;
}

L’initialisation paresseuse (lazy) de useState crée l’instance Uppy une seule fois par composant, et non à chaque rendu. Uppy propose également des wrappers pour Vue, Svelte et Angular.

Quand ne pas utiliser Uppy

Un seul petit fichier, comme un avatar de profil, ne justifie ni Uppy ni Tus. Une simple requête POST de formulaire vers votre propre endpoint suffit, tandis que Tus ajouterait une intégration serveur qu’il faudrait ensuite maintenir. Si vous souhaitez plus tard bénéficier de l’interface d’Uppy tout en conservant un endpoint HTTP classique, @uppy/xhr-upload répond à ce besoin : il peut envoyer un upload de formulaire multipart standard ou une requête PUT. Notre guide sur l’upload de fichiers avec Multer dans Node présente la partie réception.

Conclusion

Les uploads avec reprise sont la principale raison d’adopter Uppy. Uppy 6.0 en simplifie l’exploitation, avec un plugin S3 unique n’utilisant qu’un seul mode de signature par configuration, et un package core unique au lieu de cinq qu’il fallait maintenir synchronisés. Pour commencer, installez @uppy/core, @uppy/dashboard et @uppy/tus, pointez le plugin Tus vers une instance tusd, puis uploadez un fichier volumineux en bridant votre connexion réseau. Si vous êtes déjà en version 5.x, commencez par les changements liés à Companion et à upload-error, car ce sont eux qui risquent de casser le code existant.

FAQ

Que doit renvoyer la fonction getCredentials de @uppy/aws-s3 ?

Elle renvoie un objet contenant une propriété credentials qui regroupe des identifiants temporaires en camelCase (accessKeyId, secretAccessKey et sessionToken), ainsi qu'un champ expiration facultatif, et une propriété region indiquant la région pour laquelle signer. AWS STS renvoie ces clés en PascalCase : pensez donc à les convertir au préalable. Uppy met le résultat en cache pendant toute la durée de vie du plugin. En cas d'erreur ExpiredToken ou InvalidAccessKeyId, il récupère de nouveaux identifiants et réessaie une fois. Restreignez au maximum la politique associée, car ces identifiants sont détenus par le navigateur.

Quelles options de @uppy/aws-s3 ont été supprimées dans Uppy 6.0, et shouldUseMultipart fonctionne-t-il toujours ?

shouldUseMultipart fonctionne toujours dans @uppy/aws-s3 6.0, tout comme getChunkSize, allowedMetaFields et limit. Les options supprimées sont endpoint, headers, cookiesRule, getTemporarySecurityCredentials, getUploadParameters, signPart, createMultipartUpload, listParts, abortMultipartUpload, completeMultipartUpload, uploadPartBytes et retryDelays. Pour migrer une configuration Companion existante, il suffit de renommer endpoint en companionEndpoint. headers et cookiesRule n'ont pas d'équivalent direct : les cookies same-origin continuent de parvenir à Companion, mais les jetons bearer ou les cookies cross-site nécessitent une autre configuration, par exemple un proxy d'authentification placé devant Companion.

Quelle est la différence entre les uploads avec reprise de Tus et @uppy/golden-retriever ?

Tus permet au serveur de conserver les octets déjà reçus afin que le transfert se poursuive, tandis que Golden Retriever restaure la sélection de fichiers côté navigateur et l'état d'Uppy après un plantage ou la fermeture d'un onglet. Dans Uppy 6.0, Golden Retriever stocke par défaut les métadonnées, l'état et les fichiers locaux jusqu'à 10 Mio dans IndexedDB, avec un repli sur localStorage pour les métadonnées. Les fichiers plus volumineux nécessitent le stockage facultatif basé sur un Service Worker, qui survit à la fermeture des onglets mais pas au redémarrage du navigateur.

Un client Uppy 6 peut-il se connecter à un serveur Companion plus ancien ?

Non. Un client Uppy 6 nécessite Companion 7, car les jetons OAuth transitent désormais par la connexion WebSocket de Companion au lieu de window.opener. Companion 7 conserve postMessage comme solution de repli pour les clients Uppy antérieurs à la version 6.0 : déployez donc d'abord Companion 7, puis mettez à niveau le client. Côté serveur, passez à companion.socket() le même objet d'options que celui que vous passez à companion.app(). Les utilisateurs de Companion hébergé n'ont aucune modification à apporter.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.