Skip to content

feat(channel): canal EvoHub — proxy da Meta Cloud (Fase 1)#2577

Merged
DavidsonGomes merged 5 commits into
developfrom
feat/evohub-channel-phase1
Jun 10, 2026
Merged

feat(channel): canal EvoHub — proxy da Meta Cloud (Fase 1)#2577
DavidsonGomes merged 5 commits into
developfrom
feat/evohub-channel-phase1

Conversation

@DavidsonGomes

Copy link
Copy Markdown
Member

O que é

Adiciona o canal EvoHub ao evolution-api: um canal adicional que espelha o canal Meta (WhatsApp Cloud), roteado pelo proxy transparente do EvoHub. O canal Meta existente continua funcionando byte-a-byte — zero regressão.

Arquitetura

  • Data plane (mensagens): EvoHubStartupService extends BusinessStartupService sobrescreve só o transporte (URL {HUB}/meta sem versão + Bearer channel_token). Toda a lógica de mensagem/eventos/persistência é herdada intacta do Meta.
  • Control plane (provisionamento): EvoHubClient + rotas finas /evohub/* (apikey global) que delegam ao hub. O front nunca fala com o hub direto nem manuseia a API-key.
  • Webhook inbound: EvoHubController reusa o parser do Meta + valida HMAC X-Hub-Signature-256 sobre o raw body (register-with-own-secret).

Edição mínima na base Meta (autorizada)

whatsapp.business.service.ts: 3 private→protected (JS emitido idêntico) + extração do helper fetchMediaFromGraph (refactor comportamentalmente neutro). Necessário porque uma subclasse não pode sobrescrever método private em TS (TS2415), e o bloco de download de mídia inline (S3) precisava do mesmo override para não vazar mídia inbound EvoHub para a Meta.

Fluxos (Fase 1)

  • Vincular existente: POST /evohub/link-existing resolve token + phone_number_id via GET /api/v1/channels/:id server-side e cria a Instance.
  • Criar novo: POST /evohub/provision cria o canal no hub + registra o webhook + devolve o public_link.

Config (.env)

EVOLUTION_HUB_URL, EVOLUTION_HUB_API_KEY, EVOLUTION_HUB_WEBHOOK_SECRET, EVOLUTION_HUB_TOKEN_WEBHOOK, EVOLUTION_HUB_FRONTEND_URL.

Acompanha

Verificação

  • tsc --noEmit ✅ · tsup build ✅ · eslint ✅
  • Regressão Meta confirmada por inspeção do diff (transporte byte-a-byte).
  • Testado ponta-a-ponta: provision, link-existing, available-channels, webhook inbound.

EvoHub é um canal adicional, espelho transparente do canal Meta (Cloud API)
roteado pelo proxy do EvoHub. Não altera o comportamento do canal Meta existente.

Data-plane: EvoHubStartupService extends BusinessStartupService, sobrescreve só
o transporte (URL {HUB}/meta sem versão + Bearer channel_token).

Control-plane: EvoHubClient + rotas /evohub/* (apikey global). Fase 1 entrega o
fluxo link-existing (resolve token + phone_number_id server-side via
GET /api/v1/channels/:id; o front nunca vê o token).

Webhook: EvoHubController reusa o parser Meta + valida HMAC X-Hub-Signature-256
sobre raw body (soft-mode na Fase 1).

Edição mínima na base Meta (autorizada): 3 private→protected + helper
fetchMediaFromGraph (JS emitido idêntico; bloco S3 inline roteado pelo helper
p/ evitar vazamento de mídia inbound EvoHub para a Meta com S3 ligado).

tsc --noEmit verde, eslint limpo, tsup build success.
…ints

- listChannels: o hub retorna { channels: [...], count } (channel_handler.go
  GetChannels), não um array nu — causava 'channels.filter is not a function'
  na rota /evohub/available-channels. Adiciona normalizeChannelList (tolera
  channels|data|array nu).
- getPlan/getMetaAppOptions: corrige paths para /me/plan e /me/meta-app-options
  (endpoints self-service do hub; /plan é admin-por-id e exige UUID). Fase 2.
… filtros whatsapp

- evohub.client: listChannels normaliza a resposta do hub { channels: [] }
  (não array nu) — corrige 'channels.filter is not a function'.
- evohub.client.provisionChannel: monta o request correto do hub { name, type,
  channel_credentials_id?, webhook_url } (antes passava o body cru → 400) e
  registra o webhook do evolution-api (single-shot, register-with-own-secret).
  Normaliza a resposta { channel } | plano.
- getPlan/getMetaAppOptions: paths corretos /me/plan e /me/meta-app-options.
- controlplane.router: /provision mapeia instanceName→name, fixa type=whatsapp,
  monta webhook_url do SERVER.URL; /available-channels filtra só type=whatsapp
  (evolution-api é API de WhatsApp; o hub devolve fb/ig também).
- env: EVOLUTION_HUB_FRONTEND_URL default corrigido p/ o domínio do hub.
Atualiza manager/dist com o build do evolution-manager-v2 incluindo a opção
de canal EvoHub na criação de instância (vincular-existente + criar-novo).

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @DavidsonGomes, your pull request is larger than the review limit of 150000 diff characters

@DavidsonGomes DavidsonGomes merged commit f742997 into develop Jun 10, 2026
4 of 5 checks passed
* server-side; o front NUNCA vê o token.
*/
async getChannel(id: string): Promise<HubChannel> {
const { data } = await this.http.get(`/channels/${id}`);
* Evolution; 'byo' exige channel_credentials no hub.
*/
async connectToMeta(channelId: string, req: MetaConnectRequest): Promise<MetaConnectResponse> {
const { data } = await this.http.post(`/channels/${channelId}/meta-connect`, req);
this.router
.get(this.routerPath('webhook/evohub', false), async (req, res) => {
if (req.query['hub.verify_token'] === configService.get<EvolutionHub>('EVOLUTION_HUB').TOKEN_WEBHOOK)
res.send(req.query['hub.challenge']);
Comment on lines +20 to +31
.post(this.routerPath('webhook/evohub', false), async (req, res) => {
const signature = req.headers['x-hub-signature-256'] as string | undefined;
const ok = evoHubController.verifyHmac((req as any).rawBody, signature);
if (!ok) {
return res.status(401).json({ error: 'invalid signature' });
}

const { body } = req;
const response = await evoHubController.receiveWebhook(body);

return res.status(200).json(response);
});
@DavidsonGomes DavidsonGomes deleted the feat/evohub-channel-phase1 branch June 10, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants