Ephemeral Downloader
Proxy de download efêmero: a sua VPS baixa o arquivo por uma rota melhor e entrega para o seu navegador, com os arquivos apagados automaticamente depois.
Sobre
Baixar um arquivo grande direto de um host congestionado costuma ser lento, e muitas vezes o gargalo não é a sua internet nem o servidor de origem, e sim o caminho entre eles. O Ephemeral Downloader coloca a sua VPS no meio: o servidor faz o download (normalmente com peering melhor), mostra o progresso em tempo real e entrega o arquivo para você pelo navegador.
O projeto é focado em uso efêmero. O arquivo fica em um diretório temporário da sessão no servidor e é apagado quando a sessão expira (1 hora sem interação). Não é um proxy de streaming puro: o download passa pelo disco do servidor antes de chegar até você.
Como funciona
navegador (React + Vite)
│ POST /api/queue {url} GET /api/tasks (polling de 2 s)
▼ ▲
Fastify (server/) ──undici GET──▶ host de origem │
│ │
▼ │
server/storage/<id-da-sessao>/<task>_<arquivo> ────────┘
▲
│ GET /api/download/:taskId (arquivo concluído)
navegador
POST /api/queueenfileira a URL e o backend baixa comundici, gravando emserver/storage/<id-da-sessão>/por um pipeline (respeita backpressure e só marca a task como concluída depois do arquivo em disco).- O frontend faz polling de
GET /api/tasksa cada 2 segundos e mostra status, progresso, bytes recebidos, total e velocidade em MB/s e Mbps. - Quando a task conclui, o botão "Save to Disk" busca
GET /api/download/:taskIde salva o arquivo. - Em produção o próprio Fastify serve o build do Vite na raiz, com fallback da SPA.
GET /streamé o proxy antigo, mantido apenas respondendo 410 (descontinuado).
Stack
| Camada | Escolha |
|---|---|
| Runtime | Node.js 20.19 ou superior |
| Backend | Fastify 5, undici 7 (cliente HTTP), zod (validação) |
| Frontend | React 19, Vite 7, Tailwind CSS 4, lucide-react |
| Linguagem | TypeScript 5.9 |
| Monorepo | pnpm workspaces (server e web) |
| Deploy | nixpacks (nixpacks.toml) |
Requisitos
- Node.js 20.19 ou superior para rodar e fazer o build (exigência do Vite 7 e do undici 7). Para rodar os testes (vitest 5) use Node 22.12 ou superior.
- pnpm 9 ou superior (verificado no pnpm 12.8.1 com Node 26.7.0; o lockfile é o formato 9.0)
- Portas livres: backend (padrão 3000) e Vite (5173) em desenvolvimento
Observação sobre pnpm: a seção overrides do pnpm-workspace.yaml (fastify e undici com versão mínima de segurança) só é aplicada pelo pnpm 10 ou superior. Com pnpm 9 ela é ignorada.
Início rápido
pnpm install
# desenvolvimento: Fastify (3000) + Vite (5173) juntos
pnpm dev
# abra http://localhost:5173
Em desenvolvimento o Vite encaminha /api para o backend (proxy configurado em web/vite.config.ts), então não é preciso arquivo .env. Se a porta 3000 estiver ocupada:
PORT=3100 VITE_API_PROXY=http://localhost:3100 pnpm dev
Uso
- Cole a URL direta (http ou https) de um arquivo e clique em Download.
- A lista de downloads mostra status, progresso, tamanho e velocidade, atualizada a cada 2 segundos.
- Quando o download conclui, use Save to Disk para salvar o arquivo na sua máquina.
- A sessão dura 1 hora de inatividade (o contador reinicia a cada interação) e depois os arquivos são apagados do servidor.
API
| Método | Rota | O que faz |
|---|---|---|
| GET | /health |
Devolve {"status":"ok"}. |
| GET | / |
Serve a SPA (build do Vite). |
| GET | /api/session |
Cria ou recupera a sessão e devolve sessionId, expiresAt e remainingMs. O id também vem no header x-session-id. |
| POST | /api/queue |
Corpo {"url":"https://..."}. Enfileira e inicia o download, devolvendo a task criada. |
| GET | /api/tasks |
Lista as tasks da sessão (usado no polling da interface). |
| GET | /api/download/:taskId |
Entrega o arquivo concluído da task. |
| GET | /stream |
Descontinuado: responde 410. |
A sessão é identificada pelo header x-session-id (a interface guarda o id em localStorage). Erros observados: 400 para URL inválida ({"error":"Invalid URL"}), com protocolo que não seja http/https, apontando para endereço local ou privado, ou para download não pronto; 403 para task de outra sessão e 404 para task inexistente. Se a origem falhar (por exemplo, 404 no host de origem), a task fica com status: "error" e a mensagem em error. O servidor segue até 5 redirects; um nome de domínio que resolva para IP privado também faz a task terminar em erro.
Produção
pnpm build # server: tsc | web: tsc -b && vite build
PORT=3000 pnpm start # node server/dist/index.js: API + build do front na mesma porta
O nixpacks.toml descreve esse fluxo: build em pnpm install --frozen-lockfile && pnpm build e start em pnpm start.
Configuração
| Variável | Onde | Padrão | O que faz |
|---|---|---|---|
PORT |
backend | 3000 |
Porta do Fastify. |
STORAGE_ROOT |
backend | server/storage |
Diretório onde ficam os arquivos das sessões. |
ALLOW_PRIVATE_NETWORK |
backend | desligado | Com true, permite baixar de localhost e de endereços de rede privada. Por padrão o servidor recusa esses destinos (proteção contra SSRF). |
VITE_API_URL |
frontend | mesma origem | Host da API, se for diferente do que serve o front. |
VITE_API_PROXY |
frontend (dev) | http://localhost:3000 |
Alvo do proxy de /api no Vite. |
Estrutura do projeto
server/ backend Fastify
├── src/index.ts rotas (/health, /api/*), CORS e static do front
├── src/lib/
│ ├── DownloadManager.ts download da origem com undici e gravação na sessão
│ ├── SessionManager.ts sessões, expiração (1 h) e limpeza dos arquivos
│ └── netGuard.ts validação de URL e bloqueio de destinos privados (SSRF)
└── test/ testes (vitest)
web/ frontend React + Vite
├── src/context/ SessionContext (sessão e polling das tasks)
├── src/components/ ui/ (Input, Button, Card, Alert...) e feature/ (form, item, timer)
└── vite.config.ts proxy de /api em desenvolvimento
.agent/ scaffolding de agentes de IA (não faz parte do runtime)
.github/workflows/ CI (typecheck, lint, testes e build)
nixpacks.toml comandos de build e start do deploy
pnpm-workspace.yaml workspaces, allowBuilds e overrides
Testes e desenvolvimento
pnpm typecheck # tsc nos dois pacotes
pnpm lint # eslint no web
pnpm test # vitest no server e no web
pnpm build
Os testes do servidor cobrem a validação de URL e o bloqueio de IPs privados, o tratamento de redirects, a expiração de sessões (com remoção das tasks e dos arquivos) e a validação do id de sessão. Os do web cobrem os estados do componente DownloadItem. O mesmo conjunto roda no GitHub Actions (.github/workflows/ci.yml).
Estado atual e limitações
- Sessões e tasks ficam em memória: reiniciar o servidor perde a lista de tasks (os arquivos em disco são removidos pelo coletor depois de 1 hora).
- Sem autenticação: qualquer um que alcance o servidor pode enfileirar downloads (de destinos públicos). Coloque atrás de VPN, autenticação reversa ou firewall.
- Não há retomada de download nem uso de
Rangeno fluxo atual (o parâmetrooffsetdo antigo/streamnão existe mais). - Não há limite de tamanho, banda ou cota: um link muito grande ocupa o disco do servidor até a sessão expirar.
- "Save to Disk" carrega o arquivo inteiro na memória do navegador (
blob()) antes de salvar, o que pesa em arquivos muito grandes. - O diretório
.agent/é scaffolding de ferramentas de IA e não é usado pela aplicação.
Licença
Sem licença definida no repositório: não existe arquivo LICENSE e o package.json está marcado como private. Defina uma licença antes de reutilizar ou publicar o código.