# Fases — Plugin Inbox (NewWhats como Motor Central) **Data de início:** 2026-04-17 **Última atualização:** 2026-04-17 **Estratégia:** Contrato mínimo (A) → PoC validação (B) → expansão incremental --- ## ✅ Fase 1 — Contrato Mínimo — CONCLUÍDA **Objetivo:** Definir o núcleo da API e do protocolo WS antes de escrever qualquer código. **Concluída em:** 2026-04-17 ### REST — motor `/api/ext/v1/` | Endpoint | Status | |---|---| | `GET /sessions` | ✅ | | `GET /sessions/:id/qr` | ✅ | | `GET /inbox?session=&search=&limit=` | ✅ | | `GET /inbox/:chatId/messages?limit=&before=` | ✅ | | `POST /inbox/:chatId/send` | ✅ | ### WebSocket — `WS /api/ext/v1/stream` | Evento | Emissor | |---|---| | `connected` | bridge (handshake) | | `session.qr` | `WhatsAppConnectionManager` via hookBus | | `session.status` | `WhatsAppConnectionManager` via hookBus | | `message.new` | `MessageHandler` via hookBus | ### Envelope padrão (contrato imutável v1) ```json { "event": "session.qr", "data": { "instanceId": "uuid", "qrBase64": "data:image/png;base64,..." }, "timestamp": 1776403129107 } ``` ### Decisões técnicas fixadas - **WebSocket nativo** (`ws` lib, `noServer: true`) — sem dependência de Socket.IO client - **Versionamento:** `/api/ext/v1/` — quebra de contrato → `/v2/`, nunca silenciosamente - **Auth:** header `x-nw-key: ` — validado contra tabela `api_keys` (Prisma) - **Rate-limit:** 120 req/min por chave (in-memory, janela deslizante) - **Multi-tenant:** `integration_key` → `tenantId` — eventos filtrados antes de enviar ao WS - **hookBus:** `WhatsAppConnectionManager` e `MessageHandler` emitem `ext:*`; plugin `ext-api` subscreve ### Arquivos criados/modificados **Motor:** - `backend/src/core/types.ts` — `httpServer: HttpServer` no `PluginContext` - `backend/src/server.ts` — expõe `httpServer` no `pluginCtx` + `globalThis.__whatsAppManager` - `backend/src/modules/whatsapp/connection/WhatsAppConnectionManager.ts` — emite `ext:session.qr`, `ext:session.status` - `backend/src/modules/whatsapp/handlers/MessageHandler.ts` — emite `ext:message.new` - `plugins/ext-api/manifest.json`, `index.ts`, `backend/apikey-auth.ts`, `backend/routes.ts`, `backend/ws-bridge.ts` --- ## ✅ Fase 2 — PoC QR + WS + Inbox — CONCLUÍDA **Objetivo:** Validar fluxo end-to-end + inbox completo. **Concluída em:** 2026-04-17 ### Entregue (absorveu escopo da Fase 3 original) - Tela de 3 painéis no admin-app (`/wa-inbox`) - Painel Sessões: lista com status badge, QR inline em tempo real - Painel Chats: lista com preview, timestamp relativo, badge unread, busca - Painel Mensagens: histórico, bubbles por lado, status (✓ / ✓✓ / ✓✓ ciano), envio - Append em tempo real via WS (`message.new`) - Update optimista no envio (rollback em erro) - Reconexão automática do WS (retry a cada 3s com badge de status) ### Proxy no satélite **Satélite:** - `plugins/newwhats/proxy.js` — proxy REST (HTTP nativo) + proxy WS (TCP tunnel via `net`) - `plugins/newwhats/index.js` — proxy REST em `/api/nw/v1/*`, WS em `/api/nw/v1/stream` - `plugins/index.js` — `PluginManager` recebe `httpServer` como 3º argumento - `server.js` — cria `http.createServer(app)` explicitamente antes de `loadAll()` **Admin-app:** - `services/api.ts` — `NwService` + interfaces `NwSession`, `NwChat`, `NwMessage` - `pages/WhatsAppInbox.tsx` — página de inbox - `App.tsx` — rota `/wa-inbox` + item de menu ### Fluxo validado ``` Browser → /api/nw/v1/sessions → satélite proxy → motor → retorna instâncias ✅ Browser → WS /api/nw/v1/stream → TCP tunnel → motor WS → handshake OK ✅ Baileys gera QR → hookBus → ws-bridge → browser QR inline ✅ QR escaneado → CONNECTED → browser recarrega chats ✅ Mensagem recebida → hookBus → browser appenda ✅ ``` --- ## ✅ Fase 3 — Inbox Refinado — CONCLUÍDA **Concluída em:** 2026-04-17 ### Motor — novos endpoints | Endpoint | Função | |---|---| | `POST /inbox/:chatId/read` | Zera `unreadCount` | | `PATCH /inbox/:chatId/archive` | Arquiva/desarquiva chat | ### Motor — melhorias - `message.update` via hookBus — emitido em `WhatsAppConnectionManager` após mudança de status de mensagem (delivered/read) - Filtro `NOT @lid / NOT @broadcast` no SELECT de inbox — elimina 3k+ chats "fantasma" gerados pelo Baileys - Campos `pushName` e `senderJid` adicionados ao SELECT de mensagens - `syncExpiredAvatars()` no `ContactHandler` — parse do parâmetro `oe=` das URLs do CDN WhatsApp; limpa e re-enfileira avatares que expiram em < 48h ### UI — refinamentos - `markRead` ao selecionar chat, `handleArchive` com atualização otimista da lista - Handler WS para `message.update` — atualiza status da mensagem em tempo real - Paginação infinita de mensagens (scroll no topo, cursor `before=`, `IntersectionObserver`) - Grupos: exibição do nome do remetente (`pushName`) nas mensagens - Smart scroll-to-bottom: só força descida se o chat mudou; preserva posição ao carregar mais ### Arquivos modificados **Motor:** - `plugins/ext-api/backend/routes.ts` — `POST /inbox/:chatId/read`, `PATCH /inbox/:chatId/archive`, filtro `@lid/@broadcast`, campos `pushName`/`senderJid`, `message.update` no hookBus - `backend/src/modules/whatsapp/connection/WhatsAppConnectionManager.ts` — emite `ext:message.update`; chama `syncExpiredAvatars()` 30s após conectar - `backend/src/modules/whatsapp/handlers/ContactHandler.ts` — `syncExpiredAvatars()` **Satélite:** - `services/api.ts` — `markRead`, `archive`; campos `pushName`/`senderJid` em `NwMessage` - `pages/WhatsAppInbox.tsx` — paginação infinita, grupos, smart scroll, WS `message.update` --- ## ✅ Fase 4 — Mídia e Arquivos — CONCLUÍDA **Concluída em:** 2026-04-17 ### Motor — endpoint de mídia | Endpoint | Comportamento | |---|---| | `GET /media/:messageId` | Redirect 302 se URL externa (CDN/Wasabi); stream com Range se arquivo local | - `Content-Disposition: inline` para imagens/áudio/vídeo; `attachment` para documentos - `Accept-Ranges: bytes` + suporte a `Range:` — essencial para `