Protocolo tus
tus é um protocolo aberto para uploads de arquivos retomáveis. Use-o para uploads confiáveis que podem sobreviver a interrupções de rede.
Por que tus?
- Retomável: Continue uploads após falhas de rede
- Eficiente: Retome de onde parou, não do início
- Padronizado: Protocolo bem definido com muitas implementações de servidor
- Em partes: Lida automaticamente com arquivos grandes
Instalação
npm install @samithahansaka/dropup tus-js-client
tus-js-client é uma dependência peer.
Uso Básico
import { useDropup } from '@samithahansaka/dropup';
import { createTusUploader } from '@samithahansaka/dropup/tus';
function TusUploader() {
const { files, actions, state, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://tusd.tusdemo.net/files/',
}),
onUploadComplete: (file) => {
console.log('Upload completo:', file.uploadedUrl);
},
});
return (
<div>
<div {...getDropProps()} style={styles.dropzone}>
<input {...getInputProps()} />
<p>Solte arquivos para upload retomável</p>
</div>
{files.map(file => (
<div key={file.id} style={styles.fileItem}>
<span>{file.name}</span>
<span>{file.progress}%</span>
<span>{file.status}</span>
</div>
))}
<button onClick={() => actions.upload()}>
Enviar
</button>
</div>
);
}
Opções de Configuração
createTusUploader({
// Obrigatório
endpoint: 'https://seu-servidor-tus.com/files/',
// Tamanho da parte (padrão: Infinity = sem divisão)
chunkSize: 5 * 1024 * 1024, // Partes de 5MB
// Configuração de retry
retryDelays: [0, 1000, 3000, 5000], // Tentar novamente após 0s, 1s, 3s, 5s
// Headers para todas as requisições
headers: {
'Authorization': 'Bearer token',
},
// Metadados enviados ao servidor
metadata: {
filename: 'file.name',
filetype: 'file.type',
// Adicionar metadados personalizados
userId: 'user123',
},
// Habilitar retomabilidade via localStorage
storeFingerprintForResuming: true,
// Remover fingerprint após upload bem-sucedido
removeFingerprintOnSuccess: true,
// Uploads paralelos
parallelUploads: 3,
});
Retomar Uploads Anteriores
tus pode retomar uploads interrompidos:
const { files, actions } = useDropup({
upload: createTusUploader({
endpoint: 'https://seu-servidor-tus.com/files/',
// Armazenar URLs de upload para retomar
storeFingerprintForResuming: true,
// Opcional: chave de armazenamento personalizada
urlStorageKey: 'minha-app-tus-uploads',
onShouldRetry: (error, retryAttempt) => {
// Lógica de retry personalizada
if (error.message.includes('network')) {
return retryAttempt < 5;
}
return false;
},
}),
});
// Arquivos podem ser retomados automaticamente quando re-adicionados
// ou manualmente com retry:
actions.retry(['file-id']);
Rastreamento de Progresso
function TusWithProgress() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://seu-servidor-tus.com/files/',
}),
onUploadProgress: (file, progress) => {
console.log(`${file.name}: ${progress}%`);
},
onUploadStart: (file) => {
console.log(`Iniciando upload: ${file.name}`);
},
onUploadComplete: (file) => {
console.log(`Completo: ${file.uploadedUrl}`);
},
});
return (
<div>
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Solte arquivos aqui</p>
</div>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
{file.status === 'uploading' && (
<div style={styles.progressBar}>
<div
style={{
...styles.progress,
width: `${file.progress}%`,
}}
/>
</div>
)}
<span>{file.status}</span>
</div>
))}
</div>
);
}
Pausar e Retomar
function PausableUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
upload: createTusUploader({
endpoint: 'https://seu-servidor-tus.com/files/',
storeFingerprintForResuming: true,
}),
});
return (
<div>
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Solte arquivos aqui</p>
</div>
{files.map(file => (
<div key={file.id}>
<span>{file.name}</span>
<span>{file.progress}%</span>
{file.status === 'uploading' && (
<button onClick={() => actions.cancel(file.id)}>
Pausar
</button>
)}
{file.status === 'paused' && (
<button onClick={() => actions.retry([file.id])}>
Retomar
</button>
)}
{file.status === 'error' && (
<button onClick={() => actions.retry([file.id])}>
Tentar Novamente
</button>
)}
</div>
))}
<button onClick={() => actions.upload()}>
Iniciar Todos
</button>
</div>
);
}
Configuração do Servidor
tusd (Servidor Oficial)
# Instalar tusd
go install github.com/tus/tusd/cmd/tusd@latest
# Executar com opções padrão
tusd -upload-dir=/uploads
# Executar com armazenamento S3
tusd -s3-bucket=meu-bucket -s3-endpoint=https://s3.amazonaws.com
Node.js (tus-node-server)
const { Server } = require('@tus/server');
const { FileStore } = require('@tus/file-store');
const server = new Server({
path: '/files',
datastore: new FileStore({ directory: './uploads' }),
});
// Com Express
app.all('/files/*', server.handle.bind(server));
Tratamento de Metadados Personalizado
// Lado do servidor: acessar metadados
const server = new Server({
path: '/files',
datastore: new FileStore({ directory: './uploads' }),
onUploadCreate: async (req, res, upload) => {
// Acessar metadados enviados do cliente
console.log(upload.metadata);
// { filename: 'foto.jpg', filetype: 'image/jpeg', userId: 'user123' }
},
onUploadFinish: async (req, res, upload) => {
// Processar upload completo
console.log('Upload completo:', upload.id);
},
});
Extensões tus
A integração tus do Dropup suporta estas extensões tus:
| Extensão | Descrição |
|---|---|
| Creation | Criar novos uploads |
| Termination | Cancelar uploads |
| Checksum | Verificar integridade |
| Concatenation | Uploads paralelos |
| Expiration | Limpeza automática |
Tratamento de Erros
const { files, actions } = useDropup({
upload: createTusUploader({
endpoint: 'https://seu-servidor-tus.com/files/',
retryDelays: [0, 1000, 3000, 5000, 10000],
}),
onUploadError: (file, error) => {
if (error.code === 'TUS_OFFLINE') {
console.log('Rede offline, retomará quando conectar');
} else if (error.code === 'TUS_EXPIRED') {
console.log('Upload expirou, iniciando do zero');
actions.retry([file.id]);
} else {
console.error('Upload falhou:', error.message);
}
},
});
Uploads de Arquivos Grandes
tus é ideal para arquivos grandes:
function LargeFileUploader() {
const { files, actions, getDropProps, getInputProps } = useDropup({
maxSize: 10 * 1024 * 1024 * 1024, // Limite de 10GB
upload: createTusUploader({
endpoint: 'https://seu-servidor-tus.com/files/',
chunkSize: 50 * 1024 * 1024, // Partes de 50MB
retryDelays: [0, 3000, 5000, 10000, 20000],
storeFingerprintForResuming: true,
}),
onUploadProgress: (file, progress) => {
// Mostrar progresso detalhado para arquivos grandes
const uploadedMB = (file.size * progress / 100 / 1024 / 1024).toFixed(0);
const totalMB = (file.size / 1024 / 1024).toFixed(0);
console.log(`${uploadedMB}MB / ${totalMB}MB`);
},
});
return (
<div {...getDropProps()}>
<input {...getInputProps()} />
<p>Envie arquivos grandes (até 10GB)</p>
</div>
);
}
Comparação: tus vs Upload em Partes
| Recurso | tus | Partes Personalizado |
|---|---|---|
| Retomabilidade | Integrada | Manual |
| Suporte de servidor | Muitas opções | Personalizado |
| Overhead do protocolo | Maior | Menor |
| Configuração | Padronizada | Flexível |
| Melhor para | Arquivos grandes, redes instáveis | Uploads simples |