fix(crm): QR de conexão WhatsApp para de depender só do webhook, +res…#63
Conversation
…et e login por código O botão "Gerar QR" só disparava o pedido pra Evolution e esperava o QR chegar depois via webhook (QRCODE_UPDATED) + Supabase Realtime — dois elos que podem falhar/atrasar em silêncio (webhook mal configurado na VPS, Realtime desligado sem aviso, ou só a corrida entre o refresh e o webhook). Quando isso falhava, o clique não dava nenhum feedback: nem QR, nem erro. Agora a própria resposta do connect/create da Evolution (que já costuma trazer o QR no corpo) é lida e persistida na hora — o botão funciona mesmo se o canal do webhook estiver quebrado. Complementos: - Polling client-side (2.5s por até 45s) enquanto aguarda o scan, em vez de um único refresh que corre contra o webhook. - Botão "Recarregar/Reiniciar conexão": desloga + recria a instância na Evolution do zero, pra quando a sessão trava e nada resolve. - Segunda opção de login: código de pareamento (a Evolution troca o QR por um código curto digitado no WhatsApp) — o usuário nunca fica sem saída. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe CRM instance flow now supports QR and pairing-code authentication. Evolution responses expose connection credentials, CRM state persists and clears them, status polling tracks connection completion, and the management UI provides QR, pairing-code, and restart controls. ChangesCRM connection authentication
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
actor Client
participant GerenciadorInstancias
participant crm-instancias-actions
participant EvolutionAPI
participant Supabase
participant crm-inbound
Client->>GerenciadorInstancias: Generate QR or pairing code
GerenciadorInstancias->>crm-instancias-actions: Call authentication action
crm-instancias-actions->>EvolutionAPI: Create or connect instance
EvolutionAPI-->>crm-instancias-actions: Return qrBase64 or pairingCode
crm-instancias-actions->>Supabase: Persist credential and qrcode status
crm-instancias-actions-->>GerenciadorInstancias: Return authentication result
GerenciadorInstancias->>crm-instancias-actions: Poll instance status
crm-instancias-actions->>Supabase: Read connection and credential state
Supabase-->>crm-instancias-actions: Return status, QR, and pairing code
crm-instancias-actions-->>GerenciadorInstancias: Return current status
crm-inbound->>Supabase: Clear credentials after connected event
GerenciadorInstancias-->>Client: Stop polling and hide credentials
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/crm-inbound.ts (1)
201-212: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winStale QR/pairing code not cleared on disconnect.
The patch only nulls
ultimo_qr/ultimo_pairing_codewhenstatus === "conectado". If the session goes straight from"qrcode"to"close"(e.g., pairing timeout/failure) without ever connecting, the now-invalid QR/code stays in the row and is still rendered by the UI (status_conexao !== "conectado"condition inGerenciadorInstancias.tsx), misleading the user into scanning/typing a dead code.🛡️ Proposed fix
const agora = new Date().toISOString() const patch: Record<string, unknown> = { status_conexao: status, updated_at: agora, } if (status === "conectado") { patch.conectado_em = agora patch.ultimo_qr = null // QR/codigo ja foi usado, evita reexibir na UI patch.ultimo_pairing_code = null + } else if (status === "desconectado") { + patch.ultimo_qr = null + patch.ultimo_pairing_code = null }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/crm-inbound.ts` around lines 201 - 212, Update the patch construction in the connection-status update flow so ultimo_qr and ultimo_pairing_code are cleared whenever the session is no longer awaiting connection, including transitions to "close" from "qrcode". Preserve conectado_em updates only for status "conectado", and keep the existing crm_instancias update operation unchanged.
🧹 Nitpick comments (2)
components/crm/GerenciadorInstancias.tsx (1)
303-332: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPolling doesn't resume on mount, and stale codes aren't flagged after the 45s timeout.
Two related gaps in the polling lifecycle:
- If the component mounts while
instancia.status_conexao === "qrcode"(e.g., reload or navigating back mid-flow), polling never (re)starts — only explicit button clicks calliniciarPolling().- When
pararPollingfires from the 45ssetTimeout,qrLocal/pairingLocalare left as-is, so a likely-expired QR/code keeps rendering with no indication that auto-refresh has stopped.Consider starting polling on mount when already in "qrcode" status, and clearing/flagging the displayed code when the timeout elapses without connecting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@components/crm/GerenciadorInstancias.tsx` around lines 303 - 332, Update the polling lifecycle around iniciarPolling and pararPolling so the component starts polling on mount when instancia.status_conexao is "qrcode", including the appropriate effect cleanup. When the 45-second timeout expires without connection, clear or mark qrLocal and pairingLocal as expired while preserving the existing cleanup behavior for normal polling stops and successful connection.lib/evolution.ts (1)
419-436: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate error-extraction logic across
criarInstanciaEvolutionandconectarInstanciaEvolution.Both functions now inline the same message/error/status extraction instead of the existing
extrairErroRespostahelper (still used inexcluirInstanciaEvolution). Understandable since the body must be parsed once for both success and failure paths, but the duplicated 6-line block could drift over time.♻️ Suggested shared helper
+function extrairErroDeJson(json: unknown, status: number): string { + const obj = (json ?? {}) as Record<string, unknown> + return ( + (typeof obj.message === "string" && obj.message) || + (typeof obj.error === "string" && obj.error) || + `HTTP ${status}` + ) +}Then reuse
extrairErroDeJson(json, resp.status)in bothcriarInstanciaEvolutionandconectarInstanciaEvolutionfailure branches.Also applies to: 492-514
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/evolution.ts` around lines 419 - 436, Replace the duplicated inline message and status extraction in the failure branches of criarInstanciaEvolution and conectarInstanciaEvolution with the existing extrairErroDeJson(json, resp.status) helper. Preserve the single JSON parse used by each function and continue logging the full response payload before returning the helper-derived error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@components/crm/GerenciadorInstancias.tsx`:
- Around line 348-370: Update aplicarResultadoQr so iniciarPolling() also runs
when r.ok is true but neither qrBase64 nor pairingCode is provided, preserving
the existing immediate QR/pairing-code handling and notice state. Ensure
successful webhook-waiting responses begin polling for the persisted QR data.
---
Outside diff comments:
In `@lib/crm-inbound.ts`:
- Around line 201-212: Update the patch construction in the connection-status
update flow so ultimo_qr and ultimo_pairing_code are cleared whenever the
session is no longer awaiting connection, including transitions to "close" from
"qrcode". Preserve conectado_em updates only for status "conectado", and keep
the existing crm_instancias update operation unchanged.
---
Nitpick comments:
In `@components/crm/GerenciadorInstancias.tsx`:
- Around line 303-332: Update the polling lifecycle around iniciarPolling and
pararPolling so the component starts polling on mount when
instancia.status_conexao is "qrcode", including the appropriate effect cleanup.
When the 45-second timeout expires without connection, clear or mark qrLocal and
pairingLocal as expired while preserving the existing cleanup behavior for
normal polling stops and successful connection.
In `@lib/evolution.ts`:
- Around line 419-436: Replace the duplicated inline message and status
extraction in the failure branches of criarInstanciaEvolution and
conectarInstanciaEvolution with the existing extrairErroDeJson(json,
resp.status) helper. Preserve the single JSON parse used by each function and
continue logging the full response payload before returning the helper-derived
error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2c17f329-bdfd-4563-addf-5541912f61ed
📒 Files selected for processing (7)
components/crm/GerenciadorInstancias.tsxlib/crm-inbound.tslib/crm-instancias-actions.tslib/crm-leads-actions.tslib/crm-telefone.tslib/evolution.tssupabase/migrations/20260717_crm_instancias_pairing_code.sql
| function aplicarResultadoQr(r: { | ||
| ok: boolean | ||
| erro?: string | ||
| qrBase64?: string | ||
| pairingCode?: string | ||
| }) { | ||
| if (!r.ok) { | ||
| setErro(r.erro ?? "Erro") | ||
| setAviso(null) | ||
| return | ||
| } | ||
| setErro(null) | ||
| setAviso(r.erro ?? null) | ||
| if (r.qrBase64) { | ||
| setQrLocal(r.qrBase64) | ||
| setPairingLocal(null) | ||
| } | ||
| if (r.pairingCode) { | ||
| setPairingLocal(r.pairingCode) | ||
| setQrLocal(null) | ||
| } | ||
| if (r.qrBase64 || r.pairingCode) iniciarPolling() | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Polling never starts for the exact "webhook hasn't confirmed yet" case.
iniciarPolling() is only invoked when r.qrBase64 || r.pairingCode is truthy. But gerarQrAction/reiniciarInstanciaAction can legitimately return ok:true with neither field set, specifically when Evolution accepted the request but didn't return the QR inline (the exact scenario the polling was built to cover, per those actions' own comments about waiting for the webhook). In that branch, aviso shows "aguardando o webhook confirmar", but nothing then polls the DB to pick up ultimo_qr/ultimo_pairing_code once the webhook writes them — the user is stuck until they manually retry.
🐛 Proposed fix
setErro(null)
setAviso(r.erro ?? null)
if (r.qrBase64) {
setQrLocal(r.qrBase64)
setPairingLocal(null)
}
if (r.pairingCode) {
setPairingLocal(r.pairingCode)
setQrLocal(null)
}
- if (r.qrBase64 || r.pairingCode) iniciarPolling()
+ // Sempre sonda: mesmo sem QR/código na resposta, o webhook pode
+ // escrever ultimo_qr/ultimo_pairing_code em segundos.
+ iniciarPolling()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function aplicarResultadoQr(r: { | |
| ok: boolean | |
| erro?: string | |
| qrBase64?: string | |
| pairingCode?: string | |
| }) { | |
| if (!r.ok) { | |
| setErro(r.erro ?? "Erro") | |
| setAviso(null) | |
| return | |
| } | |
| setErro(null) | |
| setAviso(r.erro ?? null) | |
| if (r.qrBase64) { | |
| setQrLocal(r.qrBase64) | |
| setPairingLocal(null) | |
| } | |
| if (r.pairingCode) { | |
| setPairingLocal(r.pairingCode) | |
| setQrLocal(null) | |
| } | |
| if (r.qrBase64 || r.pairingCode) iniciarPolling() | |
| } | |
| function aplicarResultadoQr(r: { | |
| ok: boolean | |
| erro?: string | |
| qrBase64?: string | |
| pairingCode?: string | |
| }) { | |
| if (!r.ok) { | |
| setErro(r.erro ?? "Erro") | |
| setAviso(null) | |
| return | |
| } | |
| setErro(null) | |
| setAviso(r.erro ?? null) | |
| if (r.qrBase64) { | |
| setQrLocal(r.qrBase64) | |
| setPairingLocal(null) | |
| } | |
| if (r.pairingCode) { | |
| setPairingLocal(r.pairingCode) | |
| setQrLocal(null) | |
| } | |
| // Sempre sonda: mesmo sem QR/código na resposta, o webhook pode | |
| // escrever ultimo_qr/ultimo_pairing_code em segundos. | |
| iniciarPolling() | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/crm/GerenciadorInstancias.tsx` around lines 348 - 370, Update
aplicarResultadoQr so iniciarPolling() also runs when r.ok is true but neither
qrBase64 nor pairingCode is provided, preserving the existing immediate
QR/pairing-code handling and notice state. Ensure successful webhook-waiting
responses begin polling for the persisted QR data.
…et e login por código
O botão "Gerar QR" só disparava o pedido pra Evolution e esperava o QR chegar depois via webhook (QRCODE_UPDATED) + Supabase Realtime — dois elos que podem falhar/atrasar em silêncio (webhook mal configurado na VPS, Realtime desligado sem aviso, ou só a corrida entre o refresh e o webhook). Quando isso falhava, o clique não dava nenhum feedback: nem QR, nem erro.
Agora a própria resposta do connect/create da Evolution (que já costuma trazer o QR no corpo) é lida e persistida na hora — o botão funciona mesmo se o canal do webhook estiver quebrado. Complementos:
Summary by CodeRabbit
Novos Recursos
Melhorias