Uppy 6.0 e uploads de arquivos retomáveis
Configure uploads retomáveis com Uppy e Tus, veja as mudanças do Uppy 6.0 na assinatura S3 e nos pacotes core e confira as etapas para migrar da versão 5.
O Uppy é um uploader de arquivos open source em JavaScript. Combinado com o plugin @uppy/tus, ele retoma uploads grandes após uma queda de conexão ou o fechamento de uma aba, sem que o usuário precise enviar o arquivo de novo desde o início.
A maioria das equipes recorre a uma biblioteca depois do mesmo relato de bug: alguém no Wi-Fi do hotel chegou a 90% de um vídeo de 2 GB, a conexão caiu e o upload voltou para zero. Se você construiu seu próprio uploader a partir dos nossos guias sobre como criar uma dropzone de upload de arquivos ou upload de arquivos personalizado em React, é nesse ponto que essa abordagem deixa de ser suficiente.
Este artigo explica por que os uploads retomáveis são importantes, mostra uma configuração mínima do Uppy, apresenta o que mudou no Uppy 6.0 e quanto trabalho exige a atualização a partir da versão 5.x.
Principais pontos
- O Tus é um protocolo aberto, construído sobre HTTP, que torna os uploads retomáveis. Uma queda de conexão ou uma aba fechada não faz perder o progresso: o upload continua a partir do último byte recebido pelo servidor.
- O Uppy 6.0 reescreve o
@uppy/aws-s3em torno de três modos de assinatura (getCredentials,signRequestecompanionEndpoint), e cada configuração usa exatamente um deles. - No Uppy 6.0,
@uppy/utils,@uppy/store-default,@uppy/companion-cliente@uppy/provider-viewspassam a ser distribuídos dentro do@uppy/corecomo subpath exports. - Projetos que usam o metapacote
uppyou o bundle via CDN precisam de poucas ou nenhuma alteração de código para migrar para a versão 6.0. - O Companion auto-hospedado deve ser atualizado para o Companion 7 antes de o cliente migrar para o
@uppy/core6, porque os tokens OAuth agora trafegam pelo WebSocket do Companion. O Companion 7 também roda sobre o Express 5.
Por que usar o Uppy em vez de um uploader feito à mão?
Um uploader feito à mão, baseado em um único POST com fetch ou XMLHttpRequest, não consegue continuar de onde parou. Quando a conexão cai, todos os bytes já enviados são perdidos. O Tus resolve isso no nível do protocolo: o cliente pergunta ao servidor quanto ele já recebeu e envia apenas o restante. O session replay deixa claro o custo de não ter esse recurso: após um upload malsucedido, você pode ver o usuário voltar ao seletor de arquivos e escolher o mesmo arquivo grande outra vez.
Os uploads retomáveis com Tus exigem, de fato, uma contraparte no servidor. A documentação do @uppy/tus recomenda o tusd, e os servidores Tus podem armazenar os arquivos localmente ou em um armazenamento externo, como o S3.
Configuração mínima do Uppy com Dashboard e Tus
Três pacotes formam um uploader retomável completo: o @uppy/core mantém o estado, o @uppy/dashboard fornece a interface e o @uppy/tus cuida da transferência.
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/' });
A opção inline: true renderiza o Dashboard diretamente no elemento de destino, em vez de abri-lo como um modal. Se você preferir criar sua própria interface de progresso, nosso artigo sobre como construir uma barra de progresso de upload aborda os eventos envolvidos.
Fontes remotas, como o Google Drive, passam pelo Companion, o componente de servidor do Uppy. O Companion busca o arquivo no servidor, de modo que os bytes nunca passam pelo navegador do usuário. Um detalhe é importante aqui: a configuração chunkSize do @uppy/tus vale apenas para arquivos enviados a partir do dispositivo do usuário. Uploads que passam pelo Companion seguem a configuração chunkSize do próprio Companion; portanto, defina as duas se precisar limitar o tamanho de cada requisição.
Por padrão, o @uppy/tus tenta reenviar um chunk que falhou após uma série de intervalos de espera e limita quantos uploads são executados ao mesmo tempo. Quando o servidor retorna HTTP 429, a lógica padrão do onShouldRetry pausa todas as requisições na fila e aguarda antes de tentar novamente, percorrendo os intervalos definidos em retryDelays.
O que há de novo no Uppy 6.0
O Uppy 6.0 traz três mudanças principais: o @uppy/aws-s3 foi reescrito do zero, quatro pacotes de suporte foram incorporados ao @uppy/core e o @uppy/instagram foi removido. De acordo com o post de lançamento do Uppy 6.0, seis pacotes receberam uma nova versão major: @uppy/core, @uppy/companion, @uppy/aws-s3, @uppy/tus, @uppy/components e uppy.
No Uppy 5, o antigo plugin @uppy/aws-s3 tinha até oito callbacks, e o conjunto necessário variava de acordo com a sua configuração: upload multipart ou único, com ou sem Companion, assinatura no navegador ou em um servidor. A reescrita substitui tudo isso por três modos de assinatura, e você escolhe exatamente um:
| Modo | Onde a assinatura acontece | Precisa do Companion |
|---|---|---|
getCredentials | No navegador (SigV4), usando credenciais temporárias fornecidas pelo seu endpoint | Não |
signRequest | No seu próprio assinador | Não |
companionEndpoint | No Companion | Sim |
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()),
});
O Companion não é mais obrigatório para fazer upload para o S3. Com o getCredentials, o próprio navegador assina cada requisição, e o plugin funciona com qualquer serviço compatível com S3, incluindo Cloudflare R2, MinIO e DigitalOcean Spaces. A referência do @uppy/aws-s3 lista os campos que o getCredentials deve retornar; assim, no exemplo acima, o seu endpoint /s3-credentials precisa devolver exatamente esse formato. O post de lançamento também informa que a reescrita corrigiu bugs antigos relacionados ao backoff de novas tentativas, à expiração de credenciais, à detecção de modo offline e à pausa ou retomada de uploads multipart. Os testes de CI do plugin agora rodam contra um servidor MinIO real, em vez de mocks.
Como atualizar do Uppy 5 para o 6?
Se você usa o metapacote uppy ou o bundle via CDN, a atualização para a versão 6.0 exige poucas ou nenhuma alteração de código. Nos demais casos, siga a lista abaixo; o guia oficial de migração traz todos os detalhes:
- Pacotes individuais: mova os imports de
@uppy/utils,@uppy/store-default,@uppy/companion-cliente@uppy/provider-viewspara os subpaths correspondentes do@uppy/core. Os pacotes antigos estão marcados como deprecated no npm. - Companion auto-hospedado: atualize primeiro para o Companion 7 e só depois migre o cliente para o
@uppy/core6. O Companion 7 continua compatível com clientes Uppy mais antigos, mas um cliente Uppy 6 não funciona com o Companion 6 ou versões anteriores. O Companion 7 é construído sobre o Express 5 e não funciona como middleware em uma aplicação Express 4; por isso, migre sua aplicação para o Express 5 antes de fazer a troca. Ocompanion.socket()agora exigecompanionOptionscomo segundo argumento. Usuários do Companion hospedado não precisam fazer nada. - Handlers de erro do Tus: revise seu código de
upload-error, porque agora oresponsevem preenchido.
- 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 e CompanionClientSearchProvider também deixaram de existir. No lugar deles, importe Provider de @uppy/core/companion-client. A fusão existe para evitar uma falha específica: um lockfile com uma cópia antiga do @uppy/utils ao lado de um @uppy/core mais recente, o que deixava duas cópias do mesmo pacote em uma única árvore de dependências.
A partir do Uppy 6.0, o @uppy/tus repassa o status e o corpo da resposta do servidor para o evento upload-error e para file.response. As versões anteriores emitiam o erro sem a resposta do servidor.
uppy.on('upload-error', (file, error, response) => {
if (error.isNetworkError) return showOfflineNotice(); // placeholder helper
console.log(response?.status);
console.log(response?.body.xhr.responseText);
});
Você ainda recebe undefined em response em dois casos: quando o servidor nunca respondeu (por exemplo, quando está inacessível) ou quando o arquivo era remoto e foi enviado pelo Companion.
Usando o Uppy com React e outros frameworks
Os wrappers de framework do Uppy permitem que os componentes se inscrevam no estado do Uppy sem precisar conectar eventos manualmente. No React, o hook useUppyState do @uppy/react recebe uma instância do Uppy e um seletor, e renderiza o componente novamente quando a fatia selecionada do estado muda. A documentação da integração com React descreve a API completa.
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>;
}
A inicialização lazy do useState cria a instância do Uppy uma única vez por componente, e não a cada renderização. O Uppy também oferece wrappers para Vue, Svelte e Angular.
Quando não usar o Uppy
Um único arquivo pequeno, como a foto de perfil de um usuário, não precisa de Uppy nem de Tus. Um simples POST de formulário para o seu próprio endpoint é suficiente, e o Tus acrescentaria uma integração de servidor que você ainda teria de manter. Se mais tarde você quiser a interface do Uppy, mas mantendo um endpoint HTTP comum, o @uppy/xhr-upload atende a esse caso: ele pode enviar um upload padrão via formulário multipart ou uma requisição PUT. Nosso guia sobre upload de arquivos com Multer no Node mostra o lado que recebe os arquivos.
Conclusão
Os uploads retomáveis são o motivo pelo qual vale a pena adotar o Uppy. O Uppy 6.0 simplifica a operação, com um único plugin S3 que usa um só modo de assinatura por configuração e um único pacote core, em vez de cinco que precisavam se manter sincronizados. Para começar, instale @uppy/core, @uppy/dashboard e @uppy/tus, aponte o plugin Tus para uma instância do tusd e faça o upload de um arquivo grande com a rede limitada. Se você já usa a versão 5.x, comece pelas mudanças no Companion e no upload-error, pois são elas que podem quebrar o código existente.
Perguntas frequentes
O que a função getCredentials do @uppy/aws-s3 precisa retornar?
Ela retorna um objeto com uma propriedade credentials contendo credenciais temporárias em camelCase (accessKeyId, secretAccessKey e sessionToken), além de um expiration opcional, e uma region para a assinatura. O AWS STS retorna essas chaves em PascalCase, então faça o mapeamento antes. O Uppy armazena o resultado em cache durante todo o ciclo de vida do plugin. Diante de um erro ExpiredToken ou InvalidAccessKeyId, ele busca novas credenciais e tenta novamente uma vez. Restrinja ao máximo a policy associada, porque essas credenciais ficam no navegador.
Quais opções do @uppy/aws-s3 foram removidas no Uppy 6.0, e o shouldUseMultipart ainda funciona?
O shouldUseMultipart continua funcionando no @uppy/aws-s3 6.0, assim como getChunkSize, allowedMetaFields e limit. As opções removidas são endpoint, headers, cookiesRule, getTemporarySecurityCredentials, getUploadParameters, signPart, createMultipartUpload, listParts, abortMultipartUpload, completeMultipartUpload, uploadPartBytes e retryDelays. Configurações existentes com Companion migram renomeando endpoint para companionEndpoint. headers e cookiesRule não têm substituto direto: cookies same-origin continuam chegando ao Companion, mas bearer tokens ou cookies cross-site exigem outra configuração, como um proxy de autenticação na frente do Companion.
Qual é a diferença entre os uploads retomáveis com Tus e o @uppy/golden-retriever?
O Tus permite que o servidor mantenha os bytes já recebidos para que a transferência continue, enquanto o Golden Retriever restaura, no navegador, a seleção de arquivos e o estado do Uppy após um travamento ou o fechamento de uma aba. No Uppy 6.0, o Golden Retriever armazena, por padrão, metadados, estado e arquivos locais de até 10 MiB no IndexedDB, recorrendo ao localStorage para os metadados como fallback. Arquivos maiores exigem o store opcional baseado em Service Worker, que sobrevive ao fechamento de abas, mas não à reinicialização do navegador.
Um cliente Uppy 6 consegue se conectar a um servidor Companion mais antigo?
Não. Um cliente Uppy 6 precisa do Companion 7, porque os tokens OAuth agora trafegam pela conexão WebSocket do Companion em vez de window.opener. O Companion 7 mantém o postMessage como fallback para clientes Uppy anteriores à versão 6.0; portanto, faça primeiro o deploy do Companion 7 e depois atualize o cliente. No servidor, passe para o companion.socket() o mesmo objeto de opções que você passa para o companion.app(). Usuários do Companion hospedado não precisam fazer nenhuma alteração.
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