Arquitetura de Pareamento Remoto de Conexão (Remote Pairing)
📌 Contexto
Para facilitar a vida dos clientes da Pilot Status que operam como "plataforma" (vendendo para outros clientes finais), e que atualmente precisam integrar/desenhar do zero toda a parte visual do QRCode e Pairing Code, estamos introduzindo uma forma de gerar um link de pareamento.
1. Fluxo de Usuário
- O Cliente Pilot Status (via API) chama:
POST /api/v1/numbers/remote-pairingenviando o número (number) e nome (name). - A API retorna uma URL:
https://[pilotstatus.com]/connect/abc-123-xyz. - O Cliente Pilot Status envia ou exibe essa URL para o seu cliente final em um iframe ou modal simples.
- Ao abrir, o Cliente Final acessa uma página hospedada pela Pilot Status com a interface de leitura de QR Code.
- O Cliente final faz a leitura do QR e a conexão é efetuada no projeto original do Cliente Pilot Status.
- O Cliente Pilot Status é notificado via Webhook (
number.created/ status change).
Como as instâncias são provisionadas no momento da geração do link?
Para garantir que a rastreabilidade e amarrações existam de imediato no banco, o endpoint da API já cria fisicamente a WhatsAppInstance no banco de dados e nos provedores upstream.
No entanto, ela nasce com o status desconectado (CLOSE), ignorando a leitura de QR inicial que os clientes do dashboard passam. O QR Code (e seu TTL de 40s) não é ativado na API; ele só é de fato puxado (via trigger de conexão) quando o usuário final acessa a página pública e clica para parear.
🛠️ Modificações no Backend
1. Endpoint V1: Geração de Sessão de Pareamento
Caminho: apps/fullstack/src/app/api/v1/remote-pairing/route.ts
- Autenticação e Validação: Reutiliza a função
getPublicApiKeyInfodesrc/lib/public-api-auth.tspara capturar e validar as intenções de requests externos. - Armazenamento e Criação Imediata:
A
WhatsAppInstanceé efetivamente criada no banco de dados e no provedor conectado com o Pilot Status, mas gerada de maneira assíncrona sem acionar a exibição imediata do QR Code ao cliente da API.import { WhatsAppInstanceService } from "@/services/whatsapp-instance.service"; import { randomUUID } from "crypto"; const remotePairingToken = randomUUID(); // Usado na URL // Cria a instância inteira já associada ao proxy e Evolution (porém desconectada initialmente) const created = await WhatsAppInstanceService.create({ tenantId: auth.keyInfo.tenantId, displayName: parsed.data.name, number: parsed.data.number, linkMode: parsed.data.linkMode || "SINGLE", state: "CLOSE", // Nasce desconectada remotePairingToken // Salva o token rastreável no banco });
2. Endpoints Internos para a UI Pública
Caminho: apps/fullstack/src/app/api/remote-pairing-ui/[token]/route.ts
-
GET: Busca a instância que contém o token de rastreio (
remotePairingToken). Retorna dados básicos para a UI mascarar:{"valid": true, "name": "Cliente João", "maskedNumber": "+551199999****" }. -
POST: Ação engatilhada quando o usuário clica em "Gerar QR Code". Já com a instância criada, ele força uma chamada de
connectno provedor para de fato trazer o QR Code / Pairing Code vigente para aquela sessão, alterando o status interno.import { getPrimaryWhatsAppProvider, getSecondaryWhatsAppProvider } from "@pilot-status/whatsapp-provider"; // Encontra a instância via token const instance = await WhatsAppInstanceService.getByRemoteToken(params.token); // Solicita o connect passando a API Key gerada/evolution instanceId const connectResult = await primaryProvider.instance.connect(instance.instanceName, { ... }); // Atualiza banco para CONNECTING // No caso de Dual Link, repete com secondaryProvider se aplicável
💻 Modificações no Frontend
1. Página de Pareamento Pública (Next.js Application Router)
Caminho: apps/fullstack/src/app/connect/[token]/page.tsx
- Uma rota externa pura no App Router, sem dependências massivas de contextos como o
<AppProvider>autenticado. - Micro-estados reaproveitados do Flow
Numbers.tsx(SPA):- Componentização das lidas para QR Code de expiração, bem semelhantes aos tratamentos em
apps/fullstack/src/spa/pages/Numbers.tsx(onde é pego e tickado os 40s deQR_PAIRING_TTL_SECONDS). - Componente: Pode reaproveitar visivelmente chamando a função
handleGenerateQR/ ouadvanceToDualSecondaryStepse adaptados da estrutura existente que englobe os payloads:// Semelhante ao payload usado em: whatsAppConnectHasDisplayMaterial(res) { qrcodeBase64: string | null; pairingCode: string | null }
- Componentização das lidas para QR Code de expiração, bem semelhantes aos tratamentos em
- Fluxo do Componente UI:
- Aguardando: Status inicial, mostra o nome/número e um botão "Conectar Dispositivo".
- QR / Pareamento: O React vai fazer chamadas via fetch padrão pro novo backend (
/api/remote-pairing-ui/[token]).- Dual Link (Conexão Dupla): O payload que inicializou a sessão via API possuirá suporte a um parâmetro
linkMode: "DUAL". Caso ativado na intenção inicial no banco, a UI automaticamente pedirá para o cliente final parear duas vezes nativamente (Evolution GO + V2), gerando confiabilidade máxima na ponta.
- Dual Link (Conexão Dupla): O payload que inicializou a sessão via API possuirá suporte a um parâmetro
- Polling: Dá um fetch secundário em um
setIntervalem/api/remote-pairing-ui/[token]/statuspra identificar quandostate == "OPEN". - Sucesso: Animação de sucesso.
🔄 Fluxo de Confirmação (Webhook / Integração da Ponta)
Quando o pareamento remoto na página /connect/:token for finalizado, a WhatsAppInstanceService criará de fato a instância.
Novo Evento de Webhook
Para que o sistema seja perfeitamente passivo, a infraestrutura ganhará suporte ao novo evento de Webhook number.status:
- Caminho: Será adicionado nos checkboxes de controle (modais de Criação/Edição) em
apps/fullstack/src/spa/pages/Webhooks.tsxeapps/fullstack/src/spa/pages/webhooks/webhook-events.ts. - Efeito: Sempre que uma mudança de estado ocorrer (por exemplo de
CONNECTINGparaOPENou deOPENparaCLOSE), o cliente final ou o painel principal do sistema será alertado que a conexão foi concretizada ou perdida. - Em conjunto com o evento padrão
number.created, o cliente da Pilot Status armazenará as amarrações do link de pareamento sem realizar polling exaustivo.
✅ Conclusão de Migração do Fluxo
Reutilizando grande parte da infra de criação WhatsAppInstance e os webhooks transacionais do pacote @pilot-status/shared, removemos completamente do cliente a exigência de lidar com WebSockets de QR, ou timers front-end com Next.js via proxy, resolvendo a fricção de entrada e garantindo até estabilidade dual (Double Scan) sem estresse visual adicional.