Skip to content

Codex/meta coexistence upstream - #2750

Open
christianini-debug wants to merge 145 commits into
evolution-foundation:mainfrom
christianini-debug:codex/meta-coexistence-upstream
Open

christianini-debug wants to merge 145 commits into
evolution-foundation:mainfrom
christianini-debug:codex/meta-coexistence-upstream

Conversation

@christianini-debug

Copy link
Copy Markdown

📋 Description

🔗 Related Issue

Closes #(issue_number)

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🔧 Refactoring (no functional changes)
  • ⚡ Performance improvement
  • 🧹 Code cleanup
  • 🔒 Security fix

🧪 Testing

  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced
  • Tested with different connection types (if applicable)

📸 Screenshots (if applicable)

✅ Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have manually tested my changes thoroughly
  • I have verified the changes work with different scenarios
  • Any dependent changes have been merged and published

📝 Additional Notes

caiobleggi and others added 30 commits December 9, 2025 12:03
Introduces a new API endpoint and supporting logic to decrypt WhatsApp poll votes. Adds DecryptPollVoteDto, validation schema, controller method, and service logic to process and aggregate poll vote results based on poll creation message key.
Updated DecryptPollVoteDto to use a nested message.key structure and moved remoteJid to the top level. Adjusted the controller and validation schema to match the new structure for consistency and clarity.
feat(channel): add support for @newsletter in sendMessage and findChannels
…elop

Feature: Endpoint para Descriptografar e Visualizar Votos de Enquetes
…tejid-wrong-format

fix(baileys): normalize remote JIDs for consistent database lookups
…tejid-normalization-and-cache-race

fix: normalize remoteJid in message updates and handle race condition in contact cache
- Introduced a flag to prevent reconnection during instance deletion.
- Improved logging for connection updates and errors during logout.
- Added a delay before reconnection attempts to avoid rapid loops.
- Enhanced webhook headers for better tracking and debugging.
- Updated configuration to support manual Baileys version setting.
Creates new migration to ensure lid column exists even in databases
where it was previously dropped by the Kafka integration migration.

Uses prepared statement to check column existence before adding,
ensuring compatibility with both fresh and existing installations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add audio-decode library for audio buffer analysis
- Implement getAudioDuration() to extract duration from audio
- Implement getAudioWaveform() to generate 64-value waveform array
- Normalize waveform values to 0-100 range for WhatsApp compatibility
- Change audio bitrate from 128k to 48k per WhatsApp PTT requirements
- Add Baileys patch to prevent waveform overwrite
- Increase Node.js heap size for build to prevent OOM

Fixes evolution-foundation#1086
…preview

- Fix linkPreview logic in Baileys to default to true
- Add support for 'file' and 'embed' types in Typebot integration
- Ensure correct media type detection for PDFs and docs
…ng, Typebot integration, and comprehensive API functionalities for chat, group, and business profiles.
…tions, messages, groups, profiles, and integrating with Chatwoot, OpenAI, and S3.
DavidsonGomes and others added 30 commits May 6, 2026 16:28
…n/fix/logout-baileys

fix: logout instance
AxiosHeaderValue widened to include number; explicit String() cast keeps
runtime behavior and unblocks tsc --noEmit.
When EVOLUTION_OPERATOR_EMAIL is set in .env, initializeRuntime silently
calls the licensing server's /v1/register/auto endpoint on startup,
persists the returned api_key and activates the instance — skipping
the browser registration flow entirely.

Falls back to the manual registration flow on any failure (email not
yet registered, server unreachable, key suspended, etc.). Non-fatal.

Requires one prior manual registration so the email is known server-side.
…volution-foundation#2544)

After WhatsApp's LID (Linked Identity) migration, DMs to contacts
that resolve to @lid JIDs fail with a BadRequestException because
onWhatsApp returns exists:false for LID-based identifiers.

Add @lid to the whitelist alongside @broadcast in both
sendMessageWithTyping and sendPresence so these contacts are not
rejected by the existence check.

Co-authored-by: CORREIA Eduardo <eduardo.correia@hexagon.com>
…olution-foundation#2485) (evolution-foundation#2516)

The `audioWhatsapp` method was not forwarding `data.quoted` to
`sendMessageWithTyping`, so audio messages sent with a `quoted` key
were delivered as standalone messages instead of replies.

Added `quoted: data?.quoted` to the options object in both
`sendMessageWithTyping` call sites inside `audioWhatsapp` (the
encoding path and the direct path), matching the pattern used by all
other message-sending methods (textMessage, pollMessage, etc.).

Co-authored-by: octo-patch <octo-patch@github.com>
…ose' state (evolution-foundation#2420)

Evolution is a webhook-based integration that should always be considered
'open' since it passively receives events. Three changes fix the issue:

1. monitor.service.ts: Always call connectToWhatsapp() for Evolution
   instances on server restart, regardless of stored connection status.
2. instance.controller.ts: Call connectToWhatsapp() for Evolution
   instances during creation to initialize Chatwoot settings.
3. evolution.channel.service.ts: Restore stateConnection to 'open' and
   persist it to DB when connectToWhatsapp() is called in init mode.

Closes evolution-foundation#2419

https://claude.ai/code/session_013wxk4cj5U2H5fr2cxCgWdH

Co-authored-by: Claude <noreply@anthropic.com>
…ion#2546)

Prevents instances with trailing/leading whitespace in their names
from becoming undeletable via the API or UI. Fixes evolution-foundation#2543.
…foundation#2540)

* feat: add gifPlayback support for video messages

Allow gifPlayback and gifAttribution options to be passed to Baileys video messages.

* feat: add gif options to media DTOs

Add gifPlayback and gifAttribution properties to media DTO definitions.

* feat: add gif fields to media message schema

Add gifPlayback and gifAttribution validation support to media message schema.

* fix: validate gifAttribution values before assignment

Prevent invalid gifAttribution values from reaching Baileys media payload.
Stabilization release on top of 2.4.0-rc1. No DB or HTTP contract changes —
fixes in WhatsApp/Baileys (@lid bypass, audio quoted, instanceName trim) and
Evolution Channel (instances stuck in close), native GIF send support, and
optional headless licensing auto-activation via EVOLUTION_OPERATOR_EMAIL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m:error 515 (evolution-foundation#2509)

* fix(baileys): prevent healthy instances from being killed after stream:error 515

When WhatsApp sends stream:error code=515 (Connection Replaced), Baileys
handles the reconnect correctly and fires connection.update with state='open'.
However, WhatsApp then sends a 401 (loggedOut) to clean up the old session
slot, which Evolution API incorrectly treated as a real logout, killing the
newly-connected healthy instance.

The fix tracks when a stream:error 515 node arrives via the CB:stream:error
WebSocket event. If a loggedOut (401) close event fires within 30 seconds of
a 515, it is treated as a transient reconnect rather than a real logout.

Fixes evolution-foundation#2498

* fix(baileys): name the 515 reconnect grace + tighten stream-error types

Addresses sourcery-ai review feedback on the previous commit:

- Extract the 30 000ms reconnect grace window into a named class constant
  STREAM_515_RECONNECT_GRACE_MS so future tuning is self-documenting
  rather than a literal scattered through the close handler.
- Extract the magic '515' string into STREAM_ERROR_CODE_RECONNECT.
- Replace the loose 'node: any' on the 'CB:stream:error' handler with a
  minimal structural type ({ attrs?: { code?: string | number } }) so
  the payload shape is documented and type-checked.
- Compare the code via String(...) so a numeric 515 from the underlying
  socket library still triggers the grace window — the original literal
  '515' check would have silently broken on a type change.

* fix(baileys): satisfy prettier — collapse 515 reconnect guard onto one line

Check Code Quality lint failed on prettier/prettier (the
recentStream515 expression fits within the project's 120-col
printWidth on a single line, while shouldReconnect still needs to
break across two).

Co-authored-by: Octopus <liyuan851277048@icloud.com>

---------

Co-authored-by: octo-patch <octo-patch@github.com>
Co-authored-by: octo-patch <octo-patch@users.noreply.github.com>
…hName in fetchChats (evolution-foundation#2460)

* refactor(channel): remove unused pushName field

* refactor(channel): simplify pushName assignment logic

* fix(channel): remove Message.pushName fallback from MySQL CASE query

* fix(channel): remove duplicated pushName alias and prefer Contact.pushName in fetchChats
…olution-foundation#2514)

* fix(meta): handle message_echoes and guard missing contact fields

* feat(meta): fallback pushName from persisted contact on cloud api

* fix(meta): address review feedback on remoteId and status fromMe
…-foundation#2483)

* feat(chat): add fetchLid endpoint for PN to LID resolution

* fix(pr-feedback): log error when failing to fetch LID for WUID

* chore: trigger CI

---------

Co-authored-by: shennawy <m.alshenaawy@mu.edu.sa>
…(CVE pending, evolution-foundation#2435) (evolution-foundation#2549)

* security: prevent cross-instance auth bypass via query/body override (evolution-foundation#2435)

The auth guard in src/api/guards/auth.guard.ts validates instance
ownership using req.params.instanceName. The abstract router then
merged req.query (and, on /instance/create, req.body) into the
instance object via Object.assign — which silently overwrote the
already-authenticated instanceName.

An attacker with one valid token could send:

  GET /chat/findMessages/MY_INSTANCE?instanceName=VICTIM_INSTANCE

Auth passed for MY_INSTANCE, but dataValidate() then replaced the
instance with VICTIM_INSTANCE before execute() ran — giving the
caller full access to read/send messages, modify settings, and
delete other tenants' instances.

CWE-639: Authorization Bypass Through User-Controlled Key

Fix: introduce sanitizeUntrustedInput() that strips
PROTECTED_INSTANCE_FIELDS (instanceName, instanceId) from any
untrusted source before merging into the instance object. Logs a
warning on attempts so abuse is auditable.

Closes evolution-foundation#2435
Reported by @lighthousekeeper1212 via static analysis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(lint): remove extra blank line introduced by recent merge

Pre-push lint flagged whatsapp.baileys.service.ts:531 (double blank
line after the stream:error 515 fix merged via evolution-foundation#2509). Trivial
prettier fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-foundation#2577)

* fix(gitignore): exclude _evo* files from version control

* feat(channel): add EvoHub channel (Phase 1 — link-existing)

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.

* fix(evohub): normalize hub channel list response + correct self endpoints

- 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.

* fix(evohub): provision request mapping + channel list normalization + 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.

* build(manager): rebuild manager-v2 com o canal EvoHub (Fase 1)

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).
…5, ESLint 10, Redis 6, TS 6 (evolution-foundation#2575)

* chore(deps): bump baileys 7.0.0-rc.9 -> rc13 and drop obsolete waveform patch

- baileys atualizado para a ultima versao publicada (rc13)
- remove patches/baileys+7.0.0-rc.6.patch: a correcao de waveform PTT (requiresWaveformProcessing) ja foi incorporada upstream no rc13
- API additionalNodes do relayMessage confirmada identica: botoes/lista/carrossel preservados
- adiciona ATUALIZACAO-DEPENDENCIAS.md (plano de atualizacao em tiers)

tsc --noEmit e build (tsup) passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): tier 1 - safe minor/patch bumps within same major

- runtime: axios 1.17, @sentry/node 10.56, pg 8.21, dayjs, jimp, socket.io(+client) 4.8.3, minio, mediainfo.js, libphonenumber-js, jsonwebtoken, cors, fetch-socks, multer 2.1, @aws-sdk/client-sqs, prisma+@prisma/client 6.19.3, undici 7.27
- dev: prettier 3.8, tsx 4.22, @typescript-eslint/* 8.60, eslint-plugin-prettier, @types/node 24, lint-staged 16
- fix(types): cast response.headers['content-type'] as string (axios 1.17 endureceu AxiosHeaderValue) em whatsapp.baileys.service.ts e chatwoot.service.ts

tsc --noEmit e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): tier 2 - low/medium-risk majors

- uuid 14, dotenv 17, node-cron 4, i18next 26, @paralleldrive/cuid2 3, class-validator 0.15, pusher 5.3, mime-types 3 (+@types/mime-types 3), amqplib 2
- fix(rabbitmq): coerce url.port para Number (amqplib 2 exige port:number)
- mantidos na faixa de peer do baileys rc13 (evita ERESOLVE/quebra interna): audio-decode 2, link-preview-js 3, pino 9, jimp 1.6
- adiados (majors ESM-only que exigiriam mudar moduleResolution): https-proxy-agent 9, socks-proxy-agent 10, undici 8 -> mantidos em 7/8/7

tsc --noEmit e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): tier 3a - migrate to eslint 10 flat config

- eslint 8 -> 10 com eslint.config.mjs (flat config), removendo .eslintrc.js e .eslintignore
- substitui @typescript-eslint/eslint-plugin+parser pelo meta-pacote typescript-eslint 8
- troca eslint-plugin-import (sem suporte a eslint 10) por eslint-plugin-import-x 4 (regras import-x/first e import-x/no-duplicates)
- simple-import-sort 13, @commitlint/* 21, lint-staged 17, globals
- desativa regras novas do recommended do eslint 10 (preserve-caught-error, no-useless-assignment) para preservar baseline
- scripts lint/lint:check sem --ext (flat config)
- reinstalacao limpa da arvore (lock regenerado, peers do baileys respeitados)

lint:check, tsc --noEmit e build passam limpos (apos prisma generate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(deps): tier 3b - migrate to express 5

- express 4 -> 5 (+@types/express 5), movido de volta para dependencies
- rotas wildcard adaptadas ao path-to-regexp v8:
  - index.router.ts: /assets/* -> /assets/*splat (req.params.splat agora e array, juntado com '/')
  - view.router.ts: '*' -> /{*splat}
- cast 'as string' em req.params.<id> nos 7 routers de chatbot (req.params agora e string|string[] no @types/express 5; rotas :param sempre retornam string)

tsc --noEmit, lint e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(deps): tier 3c - openai 6 + remove express-async-errors (incompativel com express 5)

- openai 4 -> 6: beta.threads.runs.retrieve/submitToolOutputs agora recebem runId e thread_id no objeto de params
- remove express-async-errors (peer so suporta express 4; express 5 ja trata erros async de handlers nativamente) e seu import em abstract.router.ts

tsc --noEmit, lint e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): tier 3d - redis (node-redis) 4 -> 6

- redis 4 -> 6: cast 'as string' no retorno de get()/hGet() em rediscache.ts (redis 6/RESP3 tipa retorno como string|{}; JSON.parse exige string)
- has() ja tratava exists() > 0 (exists retorna numero)

tsc --noEmit, lint e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): tier 3e - typescript 5 -> 6

- typescript 5.7 -> 6.0.3 (compativel com typescript-eslint <6.1.0)
- adiciona 'ignoreDeprecations: 6.0' no tsconfig: baseUrl e moduleResolution=node10 viraram deprecation-error no TS 6; migracao real (node16/bundler) exigiria extensoes .js em todos os imports — adiado para o TS 7

tsc --noEmit, lint e build passam limpos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: atualiza plano de dependencias com progresso dos tiers 0-3 e notas finais

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(deps): override whatsapp-rust-bridge to 0.5.5 (fix CJS runtime resolution)

- baileys rc13 depende de whatsapp-rust-bridge@0.5.4 (ESM-only, exports so com 'import'): app nao sobe via tsx/require (ERR_PACKAGE_PATH_NOT_EXPORTED)
- 0.5.5 adiciona condicao 'require' nos exports -> resolve em contexto CommonJS (Node 24 suporta require de ESM)
- aplicado via overrides no package.json

Validado: require do modulo funciona e dev:server inicializa toda a stack ate a conexao de infra (DB/Redis).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(deps): tier 3f - moduleResolution bundler + proxy agents 9/10

- tsconfig: module CommonJS->preserve, moduleResolution node10->bundler
- remove baseUrl e ignoreDeprecations: elimina por completo a divida de deprecacao do TS 6/7 (node10 e baseUrl); paths resolvem relativo ao tsconfig (validado em tsc, tsup e tsx)
- https-proxy-agent 7->9 e socks-proxy-agent 8->10 (majors ESM-only, agora resolviveis com bundler)
- src nao usa require()/export = (compativel com module preserve)

Validado: tsc, lint, build limpos; dev:server carrega TODOS os modulos (aliases + proxy agents ESM-only) ate a fronteira de infra (auth do Postgres).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(deps)!: tier 4 - migrate to Prisma 7 (driver adapters + prisma-client generator)

BREAKING: Prisma 6 -> 7 (ESM-only, driver adapters obrigatorios)

- @prisma/client+prisma 7.8.0; adiciona @prisma/adapter-pg e @prisma/adapter-mariadb
- generator 'prisma-client-js' -> 'prisma-client' com output ./generated/client (TS gerado) nos 3 schemas (postgresql, mysql, psql_bouncer); url removida do datasource (proibida no v7)
- prisma.config.ts: connection string (datasource.url via DATABASE_CONNECTION_URI) + selecao dinamica de schema/migrations por DATABASE_PROVIDER (multi-provider)
- repository.service.ts: instancia PrismaClient com driver adapter conforme provider (PrismaPg | PrismaMariaDb)
- alias @prisma/client -> client gerado: no tsconfig (tsx/tsc) e reforcado no tsup (noExternal + esbuild alias), pois esbuild nao honra paths do tsconfig
- tsup: shims:true (shim de import.meta.url p/ client v7 no bundle CJS) e platform:node
- start:prod usa dist/main.js (CJS) - ESM puro quebra por imports sem extensao do @figuro/chatwoot-sdk
- 3 imports de JsonValue: @prisma/client/runtime/library -> /runtime/client (caminho mudou no v7)
- prisma/generated/ no .gitignore (gerado por provider via db:generate)

Validado: db:generate (postgres+mysql), tsc, lint, build; boot dev (tsx) e prod (node dist/main.js) inicializam ate a query real (DriverAdapterError: AuthenticationFailed = credenciais/infra, nao codigo). HTTP server sobe na porta 3210.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: registra Tier 3f (moduleResolution+proxies) e Tier 4 (Prisma 7) concluidos

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(audio): delega waveform/duracao ao Baileys rc13 nativo (EVO-1670)

Remove o getAudioMetadata() redundante: o Baileys rc13 ja calcula seconds/waveform nativamente (getAudioDuration/getAudioWaveform via audio-decode) quando ausentes no envio de PTT.

- removido getAudioMetadata() (~50 linhas) e o import orfao de audio-decode
- audioWhatsapp() para de passar seconds/waveform nos 2 caminhos; agora URL tambem ganha waveform
- audio-decode mantido no package.json (peerOptional usado internamente pelo Baileys)

Validado: tsc/lint/build limpos; PTT real -> resposta com seconds:9 + waveform (Uint8Array) gerados pelo Baileys; confirmado visualmente pelo usuario.

Ref: EVO-1670

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matheus Pastorini <matheus.pastorini@etus.com.br>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Davidson Gomes <davidsongviolao@gmail.com>
…pendências

- tsup.config.ts: fecha esbuildOptions antes do bloco define (erro de sintaxe
  derrubava o parse do config e abortava o tsup)
- evohub.controlplane.router.ts: normaliza req.params.id com String() — no
  Express 5 req.params é tipado string | string[] e o tsc falhava (TS2345)
- package-lock.json: sincroniza version com o package.json (2.4.0)

Refs EVO-2097
O fluxo link-existing criava a Instance mas não registrava webhook no hub
(só o provision registrava, via single-shot do POST /channels). Resultado:
canal vinculado enviava mensagens mas nunca recebia — nenhum evento inbound
chegava em /webhook/evohub e não havia erro em lugar algum.

- evohub.client.ts: novo ensureChannelWebhook(channelId, url) idempotente —
  (1) webhook já associado ao canal com a mesma URL: no-op; (2) webhook do
  usuário com a mesma URL: associa via POST /webhooks/:id/associate (se
  all_channels, nem associa); (3) cria via POST /webhooks single-shot com
  channels [channelId] e events [] (vazio = todos). Secret enviado quando
  EVOLUTION_HUB_WEBHOOK_SECRET setado (register-with-own-secret). Inclui
  HubWebhookInfo, listChannelWebhooks, listWebhooks e associateWebhook.
- evohub.controlplane.router.ts: link-existing garante o webhook ANTES de
  criar a Instance — falha do hub responde 502 sem efeito colateral (retry
  seguro); SERVER_URL vazio mantém paridade com o provision (segue sem
  webhook).
- .env.example: documenta o preenchimento das variáveis EvoHub (formato
  evh_pk_ da API key, SERVER_URL alcançável pelo hub, host local 8086,
  soft mode do webhook secret).

Refs EVO-2098
The Check Code Quality job has been red since dd552c7 and never reached its
"Check build" step: Prisma 7's prisma.config.ts resolves datasource.url via
env("DATABASE_CONNECTION_URI"), which throws PrismaConfigEnvError when the
variable is absent, and the runner has no .env (the Dockerfile copies
.env.example, which is why image builds got past this point).

prisma generate never opens a connection, so a syntactically valid URI is
enough. Without this the build gate stays dead and the next dependency bump
breaks develop unnoticed, exactly as this one did.

Also aligns the EvoHub req.params.id cast with the convention used by the
other 30 call sites in src (as string), instead of String().
ensureChannelWebhook treated any webhook whose status was not exactly
'inactive' as good enough and no-oped. But the hub also auto-moves a webhook
to status 'disabled' after repeated delivery failures, and its dispatcher
delivers ONLY when status == 'active' (webhook_dispatcher.go:294). So a
disabled webhook matched the URL, was accepted, and the channel stayed deaf —
the exact "silently not receiving" symptom this feature exists to fix, in a
common scenario (evolution-api was down long enough for the hub to disable the
webhook; the operator re-links and gets 201 but still receives nothing).

Now the guard keys on status === 'active' and reactivates a matching but
non-active webhook via PUT /webhooks/:id/status (the hub's official path out of
'disabled'), for both the channel-associated and the user-level webhook.

Also validates channel/webhook ids as UUID before interpolating them into the
hub request path (assertHubId), closing the CodeQL js/request-forgery paths the
new listChannelWebhooks method added and hardening getChannel/connectToMeta.
Hub ids are UUIDs (uuid.Parse server-side), so this rejects nothing legitimate.

Verified: tsc/eslint/build green, plus a stubbed-http harness driving
ensureChannelWebhook through the disabled/active/missing/user-inactive/
all_channels branches and the id guard (10/10 assertions).
…n/fix/EVO-2097-build-develop

fix(build): develop não compila — Express 5 quebra rotas EvoHub e tsup.config.ts com erro de sintaxe
… webhook

ensureChannelWebhook matched an existing hub webhook by URL alone and reused it
as-is. The hub only signs the inbound delivery when the webhook has a secret
stored (webhook_dispatcher.go:983) and its WebhookResponse exposes neither the
secret nor a has_secret flag (webhook.go:116), so drift is undetectable from the
client side.

A deployment that ran EvoHub phase 1 in soft mode (empty secret — the documented
default) has a hub webhook with NO secret. The moment the operator follows the
guidance this branch adds to .env.example and sets EVOLUTION_HUB_WEBHOOK_SECRET,
the next link-existing reuses that secret-less webhook, the hub delivers
unsigned, verifyHmac answers 401 and the channel is deaf — the very symptom this
feature exists to kill. Rotating the secret does the same. Worse: the webhook is
shared across channels, so the repeated 401 makes the hub auto-disable it and
every EvoHub channel stops receiving.

Now every reuse path rewrites the configured secret (PUT /webhooks/:id/secret)
BEFORE reactivating the webhook — reactivating first would just take another 401
and get auto-disabled again. Empty secret stays a no-op: the inbound accepts
unsigned payloads in soft mode.

Verified: tsc/eslint/build green, plus a stubbed-http harness driving the real
client through reuse-with-secret, disabled-webhook ordering, soft mode,
user-level webhook and the id guard (12/12 assertions).

Refs EVO-2098
The UUID guard lived in assertHubId(), a separate method. It blocks path/URL
injection at runtime just fine, but CodeQL's taint analysis does not carry a
throwing guard across a function boundary, so js/request-forgery kept flagging
every method that interpolates a hub id into the request path — the alerts the
guard was added to close stayed open.

Same check, now inline at each sink (HUB_ID.test), which is what both the
scanner and the runtime accept. Behaviour is unchanged: hub ids are UUIDs
(uuid.Parse server-side), so nothing legitimate is rejected.

Refs EVO-2098
Repo convention is English code comments; the EvoHub comments introduced by this
branch were in Portuguese. No behaviour change.

Refs EVO-2098
…n/feat/EVO-2098-evohub-link-existing-webhook

feat(evohub): registra webhook inbound no hub durante o link-existing
The only patch file (patches/baileys+7.0.0-rc.6.patch) was dropped in dd552c7
when Baileys moved to rc13, which left the directory untracked. The builder
still runs COPY ./patches ./patches before npm ci, so every image build since
has failed with: failed to compute cache key: "/patches": not found.

Keep the directory tracked (patch-package is still wired into postinstall) so
the COPY resolves and a future patch works by just dropping in a .patch file.
…n/fix/docker-build-missing-patches-dir

fix(docker): restore the patches/ directory the builder copies
Validate Meta POST signatures before dispatch, preserve business ID senders and contact names, and await status delivery.

BREAKING CHANGE: Meta POST webhooks require WA_BUSINESS_APP_SECRET. Missing configuration returns 503; invalid signatures return 401.

This branch has not been deployed

No deployments
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.