Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10752413c2 |
@@ -1,153 +1,124 @@
|
|||||||
## Ephemeral Downloader
|
<div align="center">
|
||||||
|
|
||||||
Um proxy de download efêmero para você usar a **sua VPS como ponte de alta velocidade** entre servidores lentos e a sua máquina local.
|
# Ephemeral Downloader
|
||||||
|
|
||||||
Em vez de baixar direto de um host congestionado, você aponta o link para o Ephemeral Downloader, que:
|
**Use a sua VPS como ponte de alta velocidade: ela baixa o arquivo do host lento,
|
||||||
|
guarda por até 1 hora e entrega a você — tudo por uma SPA com progresso em tempo real.**
|
||||||
|
|
||||||
- Faz o download via VPS (geralmente com rota/peering melhor).
|

|
||||||
- Transmite o arquivo por **streaming** para o seu navegador.
|

|
||||||
- Libera os recursos assim que o download termina.
|

|
||||||
|
|
||||||
Simples, focado e pensado para quem vive baixando ISOs, ROMs e arquivos grandes.
|
[Como funciona](#como-funciona) · [Execução local](#execu%C3%A7%C3%A3o-local) · [Produção](#produ%C3%A7%C3%A3o) · [Limitações](#limita%C3%A7%C3%B5es-conhecidas)
|
||||||
|
|
||||||
---
|
</div>
|
||||||
|
|
||||||
## Como funciona (visão rápida)
|
> [!NOTE]
|
||||||
|
> **Estado do projeto:** funcional, sem testes automatizados e sem CI (7 commits,
|
||||||
|
> o último em 2026-02-14). O fluxo atual **baixa primeiro para o disco da VPS** e
|
||||||
|
> só depois entrega o arquivo ao navegador — o modelo antigo de streaming direto
|
||||||
|
> (`GET /stream`, retomada por `offset`) foi aposentado e a rota responde `410`.
|
||||||
|
|
||||||
- **Frontend (React + Vite)**
|
> [!IMPORTANT]
|
||||||
|
> **Não há autenticação, cota nem lista de destinos permitidos.** Qualquer cliente
|
||||||
|
> que alcance a porta pode mandar o servidor buscar uma URL e ocupar disco. Coloque
|
||||||
|
> atrás de VPN, proxy com autenticação ou firewall.
|
||||||
|
|
||||||
- Interface SPA com campo de texto para múltiplos links (um por linha).
|
## Como funciona
|
||||||
- Lista de downloads recentes com:
|
|
||||||
- Progresso em tempo real.
|
|
||||||
- Tamanho total e já baixado.
|
|
||||||
- Velocidade em **MB/s** e **Mbps**.
|
|
||||||
- Botão para **retomar downloads** que falharam dentro da mesma sessão.
|
|
||||||
|
|
||||||
- **Backend (Fastify + Streams)**
|
Por que existe: às vezes o gargalo não é a sua internet nem o servidor de origem, e
|
||||||
- Rota `GET /stream?url=...&offset=...` que:
|
sim o caminho entre eles. Com uma VPS no meio, o arquivo vem por uma rota melhor até
|
||||||
- Faz `HEAD` na origem para validar o link e obter `Content-Length`.
|
ela, e dela até você.
|
||||||
- Faz `GET` (ou `Range: bytes=offset-` para retomada).
|
|
||||||
- Faz o **streaming direto** para o cliente, sem guardar arquivo em disco.
|
|
||||||
- Em produção, também serve o build do frontend (Vite) na própria porta do servidor.
|
|
||||||
|
|
||||||
---
|
| Etapa | O que acontece |
|
||||||
|
|---|---|
|
||||||
|
| 1. Sessão | A SPA pede `/api/session`; o servidor cria uma sessão (UUID + pasta própria) e o navegador guarda o id em `localStorage` |
|
||||||
|
| 2. Fila | Você cola um link e o servidor começa o download na hora (`queued` → `downloading` → `completed` ou `error`) |
|
||||||
|
| 3. Progresso | A SPA consulta `/api/tasks` a cada 2 s: bytes recebidos/total, porcentagem e velocidade em MB/s e Mbps |
|
||||||
|
| 4. Entrega | Com a tarefa concluída, **Save to Disk** baixa o arquivo da VPS para a sua máquina |
|
||||||
|
| 5. Limpeza | Sem atividade por **1 hora**, a sessão expira e a pasta com os arquivos é apagada; um cronômetro na tela mostra o tempo restante |
|
||||||
|
|
||||||
## Requisitos
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant N as Navegador (SPA React)
|
||||||
|
participant V as Fastify (VPS)
|
||||||
|
participant O as Servidor de origem
|
||||||
|
N->>V: GET /api/session
|
||||||
|
V-->>N: x-session-id + expiresAt
|
||||||
|
N->>V: POST /api/queue { url }
|
||||||
|
V->>O: GET url (undici)
|
||||||
|
O-->>V: bytes
|
||||||
|
V->>V: grava em storage/{sessão}/{id}_{arquivo}
|
||||||
|
loop a cada 2 s
|
||||||
|
N->>V: GET /api/tasks
|
||||||
|
V-->>N: progresso, velocidade
|
||||||
|
end
|
||||||
|
N->>V: GET /api/download/{taskId}
|
||||||
|
V-->>N: arquivo
|
||||||
|
Note over V: 1 h sem atividade → sessão e arquivos apagados
|
||||||
|
```
|
||||||
|
|
||||||
- Node.js **>= 20**
|
<details>
|
||||||
- `pnpm` **>= 8**
|
<summary>API do servidor e detalhes técnicos</summary>
|
||||||
|
|
||||||
---
|
| Rota | Função |
|
||||||
|
|---|---|
|
||||||
|
| `GET /health` | `{ "status": "ok" }` |
|
||||||
|
| `GET /api/session` | Cria ou recupera a sessão (cabeçalho `x-session-id`); devolve `sessionId`, `expiresAt` e `remainingMs` |
|
||||||
|
| `POST /api/queue` | Corpo `{ "url": "..." }`; `400` se a URL for inválida; devolve a tarefa |
|
||||||
|
| `GET /api/tasks` | Tarefas da sessão |
|
||||||
|
| `GET /api/download/:taskId` | `404` se não existe, `403` se é de outra sessão, `400` se não concluída; senão envia o arquivo e renova o prazo da sessão |
|
||||||
|
| `GET /stream` | Descontinuada: `410` |
|
||||||
|
| `GET /` e demais `GET` | `index.html` do build do Vite (`web/dist`) |
|
||||||
|
|
||||||
## Instalação
|
| Item | Implementação |
|
||||||
|
|---|---|
|
||||||
|
| Servidor | Fastify 5 + `@fastify/cors` (`origin: true`) + `@fastify/static`, validação com `zod` |
|
||||||
|
| Cliente HTTP | `undici` com agente global (até 500 conexões, `keepAliveTimeout` 10 s, `connect` 30 s) e `User-Agent: Ephemeral-Downloader/2.0` |
|
||||||
|
| Nome do arquivo | Da URL; sobrescrito por `Content-Disposition` quando existir |
|
||||||
|
| Armazenamento | `server/storage/{sessão}/{id}_{nome}`, relativo ao diretório de trabalho (`process.cwd()`); coletor a cada 1 min remove sessões expiradas e pastas órfãs com mais de 1 h |
|
||||||
|
| Frontend | React 19, Vite 7, Tailwind CSS 4, `lucide-react`; componentes em `web/src/components/{ui,feature}` |
|
||||||
|
| Estado das tarefas | Em memória (`Map`) — um restart do servidor perde a lista de tarefas |
|
||||||
|
|
||||||
Clone o repositório e instale as dependências:
|
</details>
|
||||||
|
|
||||||
|
## Limitações conhecidas
|
||||||
|
|
||||||
|
- **Sem retomada e sem lote de links.** Não há `Range`/`offset` no fluxo atual: download interrompido vira `error` e precisa ser refeito. O formulário aceita uma URL por vez.
|
||||||
|
- **Redirecionamentos.** O `DownloadManager` não define `maxRedirections`, e o `undici.request` não segue redirects por padrão; links que respondem `3xx` (comum em hosts de mirror) podem não resultar no arquivo esperado.
|
||||||
|
- **Entrega via memória do navegador.** O **Save to Disk** faz `fetch` do arquivo inteiro como `blob` antes de salvar; arquivos muito grandes pesam na memória da aba.
|
||||||
|
- **Sessão sem validação de formato.** O `x-session-id` enviado pelo cliente é usado direto para montar o caminho da pasta da sessão.
|
||||||
|
- **Sem limite de tamanho, de downloads simultâneos ou de espaço em disco** — todo link enfileirado começa imediatamente.
|
||||||
|
- `server/dist/index.js` está versionado e é do modelo antigo (só `/stream`, sem `/api/queue`): rode `pnpm build` antes de `pnpm start`.
|
||||||
|
|
||||||
|
## Execução local
|
||||||
|
|
||||||
|
Requisitos: **Node.js ≥ 20** e **pnpm ≥ 8**.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <seu-fork-ou-repo> Downloader
|
|
||||||
cd Downloader
|
|
||||||
pnpm install
|
pnpm install
|
||||||
|
pnpm dev # servidor em :3000 (tsx) e Vite em :5173
|
||||||
|
pnpm build # compila server (tsc) e web (tsc -b + vite build)
|
||||||
|
pnpm start # Fastify serve a API e o build do front pela porta 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
> [!WARNING]
|
||||||
|
> **Defina `VITE_API_URL`.** O `SessionContext` usa `""` (mesma origem) quando a
|
||||||
|
> variável falta, e o `vite.config.ts` não tem proxy — em `pnpm dev` as chamadas a
|
||||||
|
> `/api` batem no Vite e falham. Já o botão **Save to Disk** (`DownloadItem.tsx`)
|
||||||
|
> cai em `http://localhost:3000` quando falta. Crie `web/.env` com
|
||||||
|
> `VITE_API_URL=http://localhost:3000` no dev e, em produção, defina a URL
|
||||||
|
> pública **antes do `pnpm build`** (o valor é embutido no bundle).
|
||||||
|
|
||||||
## Scripts principais
|
## Produção
|
||||||
|
|
||||||
Na raiz do projeto:
|
O [`nixpacks.toml`](nixpacks.toml) descreve o build (`pnpm install --frozen-lockfile`
|
||||||
|
e `pnpm build`) e o start (`pnpm start`). O servidor lê `PORT` e usa `3000` se a
|
||||||
|
variável não existir; escuta em `0.0.0.0`.
|
||||||
|
|
||||||
- **Desenvolvimento (backend + frontend juntos)**:
|
## Avisos
|
||||||
|
|
||||||
```bash
|
- A pasta [`.agent/`](.agent) contém um kit de agentes, skills e workflows para
|
||||||
pnpm dev
|
assistentes de IA (*Antigravity Kit*, segundo o `ARCHITECTURE.md`) e não faz
|
||||||
```
|
parte da aplicação.
|
||||||
|
- Use apenas para baixar conteúdo que você tem direito de baixar.
|
||||||
Isso sobe:
|
|
||||||
|
|
||||||
- Fastify em `http://localhost:3000`
|
|
||||||
- Vite em `http://localhost:5173`
|
|
||||||
|
|
||||||
- **Build de produção (server + web)**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Start de produção (tudo pela porta 3000)**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm start
|
|
||||||
```
|
|
||||||
|
|
||||||
O servidor Fastify:
|
|
||||||
|
|
||||||
- Servirá o build do Vite na raiz (`/`).
|
|
||||||
- Continuará expondo `GET /stream` para o proxy de download.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Configuração de ambiente
|
|
||||||
|
|
||||||
### Desenvolvimento
|
|
||||||
|
|
||||||
Por padrão o frontend conversa com o backend em:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const API_URL = import.meta.env.VITE_API_URL ?? "http://localhost:3000";
|
|
||||||
```
|
|
||||||
|
|
||||||
Se você mudar a porta ou rodar o backend em outro host, basta criar um arquivo `.env` dentro de `web/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
VITE_API_URL=http://seu-backend:3000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Produção (Nixpacks ou similar)
|
|
||||||
|
|
||||||
- Defina a variável `PORT` (a maioria das plataformas faz isso automaticamente).
|
|
||||||
- O servidor Fastify faz fallback para `3000` se `PORT` não estiver definida.
|
|
||||||
- O arquivo `nixpacks.toml` já descreve:
|
|
||||||
- Comando de build: `pnpm build`
|
|
||||||
- Comando de start: `pnpm start`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fluxo de uso típico
|
|
||||||
|
|
||||||
1. Você tem uma **VPS bem localizada** (por exemplo, no mesmo continente do servidor de origem).
|
|
||||||
2. No seu desktop/notebook, abre o Ephemeral Downloader.
|
|
||||||
3. Cola um ou mais links HTTP/HTTPS diretos (por exemplo, ROMs no SourceForge).
|
|
||||||
4. Clica em **“Iniciar download”**.
|
|
||||||
5. A VPS baixa em alta velocidade e o app:
|
|
||||||
- Mostra o progresso.
|
|
||||||
- Indica tamanho total / baixado.
|
|
||||||
- Mostra velocidade agregada em MB/s e Mbps.
|
|
||||||
6. Se a conexão cair, você pode **retomar** dentro da mesma sessão.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Limitações intencionais
|
|
||||||
|
|
||||||
- **Retomada entre sessões** (fechar aba / desligar máquina) **não é suportada**.
|
|
||||||
- O projeto é focado em uso efêmero, sem manter arquivos em disco no servidor.
|
|
||||||
- O servidor realiza apenas:
|
|
||||||
- Validação básica de URL (somente `http`/`https`).
|
|
||||||
- Proxy por streaming com **backpressure** nativo do Node.js.
|
|
||||||
- Nenhuma autenticação ou controle de cota por usuário (idealmente coloque atrás de VPN, auth reverso ou firewall).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ideias futuras
|
|
||||||
|
|
||||||
- Autenticação simples para restringir o painel.
|
|
||||||
- Limite de banda por IP/usuário.
|
|
||||||
- Modo “linha de comando” usando a mesma API do servidor.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Por que esse projeto existe?
|
|
||||||
|
|
||||||
Porque às vezes o problema não é a sua internet nem o servidor de origem, e sim o **caminho entre eles**.
|
|
||||||
|
|
||||||
Colocando uma VPS no meio do caminho, você “puxa” o arquivo por uma rota melhor até a VPS e, de lá, faz o streaming até você com muito mais previsibilidade.
|
|
||||||
|
|
||||||
O Ephemeral Downloader encapsula essa ideia em uma interface simples, focada em quem só quer **colar o link, apertar um botão e acompanhar o progresso**.
|
|
||||||
Reference in new issue
Block a user